GStreamer——教程——基础教程3:Dynamic pipelines

基础教程3:Dynamic pipelines( 动态管道 )

目标

本教程显示了使用GStreamer所需的其他基本概念,它允许“动态”构建pipeline(管道),信息变得可用,而不是在应用程序开始时定义一条单一的管道。

完成本教程后,您将具备启动 Playback tutorials 。这里回顾的要点是:

  • 如何在链接elements时实现更精细的控制。

  • 如何获得有趣事件的通知以便及时做出反应。

  • 元素可以处于的各种状态。

介绍

正如您将要看到的,本教程中的管道在设置为播放状态之前尚未完全构建。这没关系。如果我们不采取进一步行动,数据将到达管道末端,管道将生成错误消息并停止。但我们将采取进一步行动.....

在这个例子中我们将打开一个多路复用的文件,音频和视频被存储在同一个容器文件中。负责打开此类容器的element称为 解复用器,容器格式的一些示例有Matroska(MKV)、Quick Time(QT、MOV)、Ogg或Advanced Systems Format(ASF、WMV、WMA)。

如果一个容器嵌入了多个流(例如,一个视频和两个音频曲目),解复用器会将它们分开,并通过不同的输出端口将它们公开。这样,可以在管道中创建不同的分支,处理不同类型的数据。

GStreamer elements相互通信的端口称为pads (GstPad)。存在sink pad(数据通过它进入元素)和src pad(数据通过它退出元素),根据定义很明显可以知道 source element 只有 src pad,sink element只有sink pad,而 filter element 两者都有。

图 1. GStreamer elements with their pads.

解复用器包含一sink pad,复用数据经过它进入element;含有多个source pads,复用数据中的每路数据各一个。

图2. A demuxer with two source pads.

为了完整起见,这里是一个包含一个解复用器和两个分支的简单pipeline,一个分支处理音频数据,一个分支处理视频数据。这不是此示例中构建的管道:

图3. 具有两个分支的示例管道。

处理解复用器的难点在于,直到收到一些数据以及有机会查看容器文件中的内容之前解复用器无法生成任何信息,即解复用器的source pad是动态生成的,在生成之前其他element无法与它连接,于是pipeline只能在这终止。

解决方案是构建从源代码到emuxer的管道,并将其设置为运行(play)。当解复用器接收到足够的信息以了解容器中流的数量和类型时,它将开始创建source pad。现在是我们完成构建pipeline 并将其连接到新添加的demuxer pads的正确时机。

为了简单起见,在本例中,只连接音频pad而忽略视频pad。

动态Hello World

将此代码复制到名为basic-tutorial-3.c(或在GStreamer安装中找到)。

basic-tutorial-3.c

#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  data.source = gst_element_factory_make ("uridecodebin", "source");
  data.convert = gst_element_factory_make ("audioconvert", "convert");
  data.resample = gst_element_factory_make ("audioresample", "resample");
  data.sink = gst_element_factory_make ("autoaudiosink", "sink");

  /* Create the empty pipeline */
  data.pipeline = gst_pipeline_new ("test-pipeline");

  if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
  if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

  /* Connect to the pad-added signal */
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

  /* Start playing */
  ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Listen to the bus */
  bus = gst_element_get_bus (data.pipeline);
  do {
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* Parse message */
    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);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  gst_object_unref (data.pipeline);
  return 0;
}

/* This function will be called by the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

需要帮助吗?

如果您需要帮助来编译此代码,请参阅构建教程平台的部分:Linux操作系统 ,Mac OS X或Windows,或在Linux上使用此特定命令:gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0`

如果您需要帮助来运行此代码,请参阅运行教程平台的部分:Linux操作系统 ,Mac OS X或Windows .

本教程仅播放音频。媒体是从Internet获取的,因此可能需要几秒钟才能启动,具体取决于您的连接速度。

所需库:gstreamer-1.0

工作流

CustomData

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

到目前为止,我们已经保存了所有需要的信息(指向GstElement指针)作为局部变量。但由于本教程(以及大多数实际应用程序)需要使用回调函数,为了便于处理我们将所有数据组织成一个结构体。

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

这是一个前向 reference,将在稍后使用。

Build Pipeline

/* Create the elements */
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.resample = gst_element_factory_make ("audioresample", "resample");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");

我们像往常一样创建元素。uridecodebin 将在内部实例化所有必要的元素(源、解复用器和解码器),以将URI转换为原始音频和/或视频流。它完成了playbin所做的一半工作。因为它包含了demuxer,它的 source pads 在初始时不可用,我们需要在运行时动态地将它们链接起来。

audioconvert是一个非常有用的插件,它能转换不同的音频格式,确保教程中的例子能够在任何平台上运行(各个平台上的音频解码器解码出的格式不一定符合audio sink的要求)。

audioresample是一个非常有用的插件,它能够转换不同的音频采样率,同样是为了确保教程中的例子能够在任何平台上运行(aduio sink不一定支持各个平台上的音频解码器解码出的音频采样率)。

autoaudiosink和前文中用到的autovideosink一样,它将把音频流渲染到声卡上。请参阅上一教程。

if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
  g_printerr ("Elements could not be linked.\n");
  gst_object_unref (data.pipeline);
  return -1;
}

在这里,我们链接了elements converter, resample and sink,但我们并不将它们与 source 连接起来,因为此时它不包含任何source pads。我们只是暂时保留这个分支(converter + sink)未链接状态,等待后续处理。

/* Set the URI to play */
g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

我们将文件的URI设置为通过属性播放,就像我们在上一个教程中所做的那样。

Signals(信号)

/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

GSignals是GStreamer中一个重点。它们将在某些事件发生的时候以回调的方式通知你。这些信号以名字属性区分,每个GObject都有它自己的信号。

在这一行中,我们正在将“pad-added”信号附加到我们的源(一个uridecodebin元素)上。为此,我们使用g_signal_connect()并提供要使用的回调函数(pad_added_handler)和一个数据指针。GStreamer不会对这个数据指针做任何处理,它只是将其转发给回调函数,以便我们可以与它共享信息。在这种情况下,我们传递了一个指向为此目的专门构建的CustomData结构的指针。

GstElement生成的信号可以在其文档中找到,或者如基础教程10:GStreamer工具所述,使用gst-inspect-1.0工具查找。

我们现在可以开始了!只需将管道设置为播放状态,并开始侦听总线以获取感兴趣的消息(如错误或结束),就像在以前的教程中一样。

回调函数

当source element最终获取到足够的信息从而开始生成数据的时候,它将创建source pads并且出发pad-added信号,这时将调用信号连接的回调函数 :

static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  • src是出发信号的element,在本教程中是uridecodebin,信号处理程序的第一个参数总是触发它的对象。

  • new_padsrc element刚刚添加的GstPad,这正是我们想要连接的pad。

  • data是我们连接信号时传递的指针,在本教程中是一个CustomData指针。

GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");

我们从CustomData中获取autoaudioconvertelement,并使用gst_element_get_static_pad()获取它的sink pad,要与new_pad连接的pad。在之前教程中我们连接elements,由GStreamer自行选择正确的pads,现在我们手动完成这部分连接。

/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
  g_print ("We are already linked. Ignoring.\n");
  goto exit;
}

uridecodebin将生成尽可能多的pads,这取决于它能够出处理的数据类型,并且没生成一个pad,回调都会被调用一次,上述代码将避免我们尝试将new_pad与一个已经连接了的element连接。

/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps (new_pad, NULL);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
  g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
  goto exit;
}

现在我们将要检查这个  new pad 将要输出的数据类型,因为我们只对产生音频的 pads 感兴趣。我们之前创建了一段处理音频的管道(一个 audioconvert 链接到audioresample和autoaudiosink ),我们将无法将其链接到一个产生视频的pad 上。

gst_pad_get_current_caps()检索pad 的当前功能(即它当前输出的数据类型),这些功能被包装在一个GstCaps结构中。可以使用gst_pad_query_caps()查询pad 可以支持的所有可能的功能。一个pad 可以提供许多功能,因此GstCaps可以包含许多GstStructure,每个代表不同的功能。pad 上的当前功能将始终具有一个单一的GstStructure,并代表一个单一的媒体格式,或者如果当前还没有功能,则返回NULL。

在这种情况下,我们知道我们想要的pad 只有一个功能(音频),我们用gst_caps_get_structure()检索第一个GstStructure。

最后,使用gst_structure_get_name()我们恢复结构的名称,其中包含格式的主要描述(实际上是其媒体类型)。

如果名称不是audio/x-raw,这不是一个解码的音频pad ,我们对其不感兴趣。

否则,尝试链接

/* Attempt the link */
ret = gst_pad_link (new_pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret)) {
  g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
  g_print ("Link succeeded (type '%s').\n", new_pad_type);
}

gst_pad_link()尝试连接两个pads。就像gst_element_link()实例一样,必须指定从source到sink的链路,并且两个pads必须属于同一个bin/pipeline中的elements。

我们完成了!当出现正确类型的pad时,它将被链接到音频处理管道的其余部分,执行将继续直到出现错误或结束。然而,我们将通过引入状态的概念来从本教程中获取更多内容。

GStreamer States

我们已经谈到了一些状态,当我们说回放只有在管道到达PLAYING状态。我们将在这里介绍其余的状态及其含义。GStreamer中有4种状态:

描述
NULLelement 的NULL状态或初始状态。
READYelement 已准备好进入PAUSED。
PAUSEDelement 被暂停,它准备接受和处理数据。然而,接收器元素只接受一个缓冲区,然后阻塞。
PLAYINGelement 正在播放,时钟正在运行,数据正在流动。

你只能在相邻的之间移动,这是,你不能从NULL到PLAYING,你必须通过中级考试READYPAUSED 状态。如果将pipeline 设置为PLAYING,不过,GStreamer将为您进行中间转换。

case GST_MESSAGE_STATE_CHANGED:
  /* We are only interested in state-changed messages from the pipeline */
  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
    GstState old_state, new_state, pending_state;
    gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
    g_print ("Pipeline state changed from %s to %s:\n",
        gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
  }
  break;

我们添加了这段代码,它侦听有关状态更改的总线消息,并将其打印在屏幕上,以帮助您理解转换。每个元素都会将有关其当前状态的消息放在总线上,因此我们将其过滤掉,只侦听来自管道的消息。

大多数应用程序只需要关心PLAYING状态能否正常播放,PAUSED状态能否正常暂停以及NULL状态能否退出和回收资源。

练习

对大多数程序员而言,动态连接一直是一个困难的话题。通过实例化一个autovideosink(可能在前方有一个videoconvert),并在正确的 pad 出现时将其链接到解复用器,证明你已经掌握了这个技能。提示:你已经在屏幕上打印出了视频 pad 的类型。

你现在应该看到(和听到)和在基础教程1:Hell world!。在您使用的教程中 playbin,这是一个方便的元素,可以自动为您处理所有的解复用和pad链接。大多数playback致力于playbin 。

总结

在本教程中,您学习了:

  • 如何使用 GSignals通知事件
  • 如何连接GstPads而不是连接它们的父elements
  • GStreamer element的各种状态

你结合以上内容构建了一条动态pipeline,这条pipeline不是在程序开始就被指定好的,而是在获取到所有媒体信息的情况下创建的。

现在,您可以继续学习基本教程,并在中了解执行搜索与时间相关的查询 基础教程4:时间管理或移动到playback教程,并进一步了解playbin元素。

请记住,在本页的附件中,您应该可以找到教程的完整源代码以及构建它所需的任何附件文件。很高兴在这里与您相遇,期待很快再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pyshark——安装、解析pcap文件

1、简介 PyShark是一个用于网络数据包捕获和分析的Python库&#xff0c;基于著名的网络协议分析工具Wireshark和其背后的libpcap/tshark库。它提供了一种便捷的方式来处理网络流量&#xff0c;适用于需要进行网络监控、调试和研究的场景。以下是PyShark的一些关键特性和使用方…

26 岁的“天才少年”,带队面壁打通高效大模型之路

每一轮技术浪潮出现时&#xff0c;冲在最前面的都是朝气蓬勃的年轻人。 当大模型代表的人工智能浪潮席卷全球&#xff0c;作为移动互联网“原住民”的年轻开发者&#xff0c;可以说是最活跃的群体。他们的脸庞还有些稚嫩&#xff0c;但在技术和方向上有着自己的想法&#xff0…

志愿服务管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;广场论坛管理&#xff0c;志愿活动管理&#xff0c;活动报名管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;志愿活动&a…

dp练习2

如何分析这个题目呢&#xff0c;要想着当前的最优解只和前面的最优解有关 class Solution { public:int numSquares(int n) {vector<int> f(n 1);for (int i 1; i < n; i) {int minn INT_MAX;for (int j 1; j * j < i; j) {minn min(minn, f[i - j * j]);}f[…

【Linux】进程_7

文章目录 五、进程8. 进程地址空间9. 进程终止10. 进程等待 未完待续 五、进程 8. 进程地址空间 我们上节知道了进程地址空间是根据页表来使虚拟地址转换成内存中的物理地址&#xff0c;那这种 地址空间 页表 的机制有什么好处呢&#xff1f;①这种机制可以将物理内存从无序…

探索 Perplexity:产品经理的新式 AI 工具

这是一篇国外博客的翻译文章&#xff0c;文中重点介绍了产品经理如何使用 AI 工具 Perplexity 来解决日常工作中的实际问题。通过深入调查和数百次电话访谈&#xff0c;收集了产品经理使用Perplexity 的具体方法&#xff0c;并列举了一些非常实用的例子。 这些方法包括理解和制…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【10】【仓库管理】【分布式基础篇总结】

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【10】【仓库管理】【分布式基础篇总结】 采购简要流程采购单采购人员的接口分布式基础篇总结参考 采购简要流程 采购单 可以搞个枚举&#xff1a; public class WareConstant {public enu…

【排序算法】希尔排序详解(C语言)

文章目录 前言希尔排序的原理原理思路 代码实现希尔排序的相关问题效率算法稳定性 前言 为什么会有希尔排序&#xff0c;要从插入排序说起&#xff0c;希尔排序一开始设计出来是为了改进插入排序&#xff0c;因为插入排序在处理大量数据时效率不高&#xff0c;特别是对于近乎有…

【数据库编程-SQLite3(三)】Ubuntu下sqlite3的使用

学习分享 1、安装sqlite3命令2、sqlite3点命令3、在Linux命令行下&#xff0c;启动sqlite33.1、编写sql脚本3.2、脚本编写--DDL3.3、进入xxx.db数据库&#xff0c;读取脚本。3.4、再次查看数据库中的表。证明表创建成功。3.5、查看数据表中用户内容3.6、查看表结构3.7、在数据库…

JAVAEE值之网络原理(1)_用户数据报协议(UDP)、概念、特点、结构、代码实例

前言 在前两节中我们介绍了UDP数据报套接字编程&#xff0c;但是并没有对UDP进行详细介绍&#xff0c;本节中我们将会详细介绍传输层中的UDP协议。 一、什么是UDP&#xff1f; UDP工作在传输层&#xff0c;用于程序之间传输数据的。数据一般包含&#xff1a;文件类型&#xff0…

【图像分割】DSNet: A Novel Way to Use Atrous Convolutions in Semantic Segmentation

DSNet: A Novel Way to Use Atrous Convolutions in Semantic Segmentation 论文链接&#xff1a;http://arxiv.org/abs/2406.03702 代码链接&#xff1a;https://github.com/takaniwa/DSNet 一、摘要 重新审视了现代卷积神经网络&#xff08;CNNs&#xff09;中的atrous卷积…

WPF 深入理解一、基础知识介绍

基础知识 本系列文章是对个人 B站 up 微软系列技术教程 记录 视频地址 https://www.bilibili.com/video/BV1HC4y1b76v/?spm_id_from333.999.0.0&vd_source0748f94a553c71a2b0125078697617e3 winform 与 wpf 异同 1.winform 项目结构 编辑主要是在 Form1.cs(页面)&#…

【QT5】<重点> QT串口编程

目录 前言 一、串口编程步骤 0. 添加串口模块 1. 自动搜索已连接的串口 2. 创建串口对象 3. 初始化串口 4. 打开串口 5. 关闭串口 6. 发送数据 7. 接收数据 二、简易串口助手 1. 实现效果 2. 程序源码 3. 实现效果二 前言 本篇记录QT串口编程相关内容&#xff0…

早期发现,健康生活!第三届ZAODX世界肿瘤早筛大会圆满落幕!

2024年6月15日-16日&#xff0c;第三届ZAODX世界肿瘤早筛大会在雄安新区盛大开幕&#xff01;本次会议由河北雄安新区管理委员会公共服务局指导&#xff0c;第三届ZAODX世界肿瘤早筛大会组委会和早筛网主办&#xff0c;粤港澳大湾区精准医学研究院&#xff08;广州&#xff09;…

Paragon NTFS for Mac 15软件下载及安装教程

简介&#xff1a; NTFS For Mac 15是首个支持Mac上读写NTFS外置存储设备解决方案 &#xff0c;解决mac不能读写外置让您更加简单直观的在Mac机上随意对NTFS文件修改、删除等操作。 安 装 包 获 取 地 址&#xff1a; Paragon Ntfs For Mac 15版&#xff1a; ​​https://sou…

04 远程访问及控制

1、SSH远程管理 SSH是一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程复制等功能。 SSH协议对通信双方的数据传输进行了加密处理&#xff08;包括用户登陆时输入得用户口令&#xff09;。 终端&#xff1a;接收用户的指令 TTY终端不能远程&#xff0c;它…

《跟我一起学“网络安全”》——等保风评加固应急响应

等保风评加固应急响应 一、安全加固 背景 随着IP技术的飞速发展&#xff0c;一个组织的信息系统经常会面临内部和外部威胁的风险&#xff0c;网络安全已经成为影响信息系统的关键问题。 虽然传统的防火墙等各类安全产品能提供外围的安全防护&#xff0c;但并不能真正彻底的消…

吴恩达2022机器学习专项课程C2W3:2.26 机器学习发展历程

目录 开发机器学习系统的过程开发机器学习案例1.问题描述2.创建监督学习算法3.解决问题4.小结 误差分析1.概述2.误差分析解决之前的问题3.小结 增加数据1.简述2.增加数据案例一3.增加数据案例二4.添加数据的技巧5.空白创建数据6.小结 迁移学习1.简述2.为什么迁移学习有作用3.小…

【设计模式-12】代理模式的代码实现及使用场景

&emsp&#xff1b;代理模式是一种应用很广发的结构性设计模式&#xff0c;它的设计初衷就是通过引入新的代理对象&#xff0c;在客户端和目标对象之间起到中介的作用&#xff0c;从而实现控制客户端对目标对象的访问&#xff0c;比如增强或者阉割某些能力。 1. 概述 代理模…

如何理解电流镜负载的差分对的增益

我们知道最普通的电阻负载的差分对的差分增益是-gmRD&#xff0c;如果我们不希望输出是双端的&#xff0c;而是希望单端输出&#xff0c;那么使用电阻负载的差分对会导致增益变为原先的一半&#xff0c;因此引入了电流镜负载的差分对&#xff0c;它可以在保证增益与原先相同的情…