GStreamer 简明教程(三):动态调整 Pipeline

news2024/11/14 20:23:50

系列文章目录

  • GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world!
  • GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline

文章目录

  • 系列文章目录
  • 前言
  • 一、静态与动态
  • 二、Basic tutorial 3: Dynamic pipelines
    • 2.1 准备阶段
    • 2.2 信号
    • 2.2 回调函数
      • 2.2.1 函数定义
      • 2.2.2 获取 pad 信息
      • 2.2.3 尝试连接
      • 2.2.4 获取 pad 的能力
    • 2.3 GStreamer 状态
      • 2.3.1 四个主要状态
      • 2.3.2 状态切换
    • 2.4 练习
  • 参考


前言

本章来了解 GStreamer 动态调整 Pipeline 的流程,这章代码略长,略微有些复杂,各位看官稍稍耐心些。

一、静态与动态

在前两章中,我们遵循固定的流程:

  1. 创建 Elements
  2. 创建 Pipeline,将 Elements 相互连接
  3. 切换 Pipeline 的状态,开始音视频任务
  4. 等待任务结束

这种情况下,我们先确定 Pipeline 的拓扑结构,然后启动它,在整个运行阶段 Pipeline 结构是不会发生变化的,它是静态的。

现在要介绍的动态调整 Pipeline 在音视频任务中是非常需要的。一个视频文件它可能包含多个流(这块基础知识请参考 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)),假设现在有个元素负责解封装叫 demuxer,可以想象到它应该有一个 sink pad 用于接收文件流数据,那么它有几个 src pad 呢?答案是取决于输入文件。如果这个视频文件中包含多个视频或者音频流,那么它有包含对应个数的 src pad。

在这里插入图片描述
在如图所示的场景中,如果一个视频文件包含两个流:音频流和视频流,那么输入给 Demuxer 后,Demuxer 会有两个输出 pad;如果只有一个视频流,那么输出 pad 的数量为 1。

然而,Demuxer 的复杂之处在于,它需要先检查文件内容才能确定自身结构。这意味着在创建之初,Demuxer 的 source pad 数量为 0,从而无法立即与其他元素进行连接。

解决这一问题的方法是:首先连接那些已知能够配对连接的元素,然后启动 Pipeline。当 Demuxer 接收到足够的文件信息以确定容器中的流数量和类型时,它将开始创建相应的 source pad。此时,我们可以继续构建管道,并将新的元素附加到 Demuxer 的新创建的输出 pad 上。

这种方法确保了灵活性和动态性,使得管道可以在运行时根据实际数据进行调整,以便正确处理多种不同的媒体文件格式。

二、Basic tutorial 3: Dynamic pipelines

2.1 准备阶段

接下来过代码实现,具体代码太长了,不贴了,参考 Basic tutorial 3: Dynamic pipelines

注意,为了简化代码,在 GStreamer 的示例中只处理了视频文件中的音频流,因此运行程序后,你可以听到声音,但看不到画面。

/* 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;

先定义了一个结构体,这个结构体中包含所有使用到的 Element。这是因为本示例会使用到回调函数,我们需要在回调函数中获取到全局的信息,因此定义这样一个结构体会方便我们处理信息。

static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

回调函数上,通过名字你大致就可以知道,这个回调函数在 pad 被添加的时候调用。

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");

继续,接下来创建了四个主要的元素,每一个元素都有特定的功能。下面是对每个元素的详细介绍:

  1. uridecodebin (source):

    • 功能: 它是一个自动探测和解码多种URI(Uniform Resource Identifier)媒体资源的元素。这个元素可以处理多种输入格式,并根据输入的媒体类型自动选择并连接合适的解码器。
    • 作用: 当你提供一个媒体文件的URI(比如文件路径、http流、rtsp流等)给这个元素,它会自动检测这个资源的类型,解析并生成相应的解码元素,以便将媒体数据传递给下一个处理元素。
  2. audioconvert (convert):

    • 功能: 这个元素用于在不同的音频格式之间进行转换。它能够处理多种音频样本格式(如从S16LE(16位小端序)到FLT(浮点型)等)以及不同的通道布局和采样率。
    • 作用: 当媒体数据被解码成原始音频流后,audioconvert元素保证这些音频数据被转换成适当的格式,以便进一步处理或播放。
  3. audioresample (resample):

    • 功能: 这个元素用于重新采样音频数据。它可以将音频数据从一个采样率转换到另一个采样率,确保音频数据满足播放或处理段的要求。
    • 作用: 例如,将48kHz的音频重新采样到44.1kHz,以适应某些播放设备或者后续处理元素的需求。
  4. autoaudiosink (sink):

    • 功能: 这个元素是一个自动音频输出插件,能够根据系统环境自动选择最适合的音频输出设备(比如ALSA、OSS、PulseAudio等)。
    • 作用: 它将处理后的音频数据输出到用户的音频设备(例如扬声器或耳机),使音频可以被播放出来。
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;
}

接着连接元素 convert -> resampler -> sink,注意这里我们并没有连接 source 元素,正如我们前面提到的,此时的 source 没有一个可用的 pad 与其他元素连接。如果你尝试连接 source 节点,gst_element_link_many 将会返回失败。

g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

最后设置 source 的播放文件路径

2.2 信号

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

GSignal 是 GStreamer 中重要的工具,它们允许你在某些有趣的事情发生时通过回调函数得到通知。信号通过名字来识别,每个 GObject 都有它自己的信号。

在这行代码中,我们在 source 元素上的 “pad-added” 信号槽中注册了一个回调函数。当 source 元素发出了 “pad-added” 信号时,它会在信号槽中逐个地调用回调函数。我们使用 g_signal_connect() 并提供要使用的回调函数 (pad_added_handler) 和一个数据指针。GStreamer 对这个数据指针不做任何处理,只是将其传递给回调函数,这样我们就可以与回调函数共享信息。在这个例子中,我们传递的是为此目的专门构建的 CustomData 结构体的指针。

GStreamer 的元素中有哪些信号呢?这个数据你可以通过文档或者 gst-inspect-1.0 工具进行查询,具体参考 GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline

uridecodebin 为例,它包含的信号信息如下:

Element Signals:

  "pad-added" :  void user_function (GstElement * object,
                                     GstPad * arg0,
                                     gpointer user_data);

  "pad-removed" :  void user_function (GstElement * object,
                                       GstPad * arg0,
                                       gpointer user_data);

  "no-more-pads" :  void user_function (GstElement * object,
                                        gpointer user_data);

  "unknown-type" :  void user_function (GstElement * object,
                                        GstPad * arg0,
                                        GstCaps * arg1,
                                        gpointer user_data);

  "autoplug-continue" :  gboolean user_function (GstElement * object,
                                                 GstPad * arg0,
                                                 GstCaps * arg1,
                                                 gpointer user_data);

  "autoplug-factories" :  GValueArray * user_function (GstElement * object,
                                                       GstPad * arg0,
                                                       GstCaps * arg1,
                                                       gpointer user_data);

  "autoplug-sort" :  GValueArray * user_function (GstElement * object,
                                                  GstPad * arg0,
                                                  GstCaps * arg1,
                                                  GValueArray * arg2,
                                                  gpointer user_data);

  "autoplug-select" :  GstAutoplugSelectResult user_function (GstElement * object,
                                                              GstPad * arg0,
                                                              GstCaps * arg1,
                                                              GstElementFactory * arg2,
                                                              gpointer user_data);

  "autoplug-query" :  gboolean user_function (GstElement * object,
                                              GstPad * arg0,
                                              GstElement * arg1,
                                              GstQuery * arg2,
                                              gpointer user_data);

  "drained" :  void user_function (GstElement * object,
                                   gpointer user_data);

  "source-setup" :  void user_function (GstElement * object,
                                        GstElement * arg0,
                                        gpointer user_data);

2.2 回调函数

2.2.1 函数定义

gst-inspect-1.0 输出中,可以看到 “pad-added” 信号的回调函数原型为:

void user_function (GstElement * object,
                    GstPad * arg0,
                    gpointer user_data);

在代码示例中,回调函数被定义为:

static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  1. src 参数表示触发信号的 GstElement,在本例中通常为 uridecodebin,因为它是唯一附加了该信号的元素。信号处理函数的第一个参数始终是触发信号的对象。

  2. new_pad 参数表示刚刚添加到 src 元素的 GstPad。通常,我们希望连接的就是这个 pad。例如,如果输入视频包括视频流和音频流,那么该回调函数将被调用两次:

    • 第一次,添加的 pad 负责输出视频数据
    • 第二次,添加的 pad 负责输出音频数据
  3. data 参数是我们在附加信号时提供的指针。在这个例子中,我们使用它来传递 CustomData 指针。

启动 Pipeline 后,当 source 元素从视频文件中获取足够的信息后,它将执行 pad 添加操作,从而触发 “pad-added” 信号并调用我们的回调函数。在回调函数中,我们需要动态调整 Pipeline 的拓扑结构,以便正确处理新添加的 pad。

目前 pipeline 结构如下图,source (uridecodebin)元素并没有与 audioconvert 相连。接下来看回调函数中的逻辑,我们在回调函数中连接 source 元素
在这里插入图片描述

2.2.2 获取 pad 信息

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

首先从 audioconvert 元素中获取它的 sink pad,这个 pad 是 source 节点想要连接的那个。

gst_element_get_static_pad 是 GStreamer 库中的一个函数,用于获取指定元素的静态 pad。静态 pad 是在元素创建时就已经存在的 pad(与动态创建的 pad 区别开来,例如通过信号 pad-added 生成的 pad)。

函数原型

GstPad* gst_element_get_static_pad(GstElement *element, const gchar *name);

参数

  • element:

    • 类型:GstElement*
    • 描述:要获取静态 pad 的元素。
  • name:

    • 类型:const gchar*
    • 描述:要获取的静态 pad 的名称。

好的,下面是对你提供的表述进行优化后的版本:


在 GStreamer 中,静态 pad 是在元素创建时就已经存在的 pad,而动态 pad 则是在特定条件下(例如状态切换)才会动态创建的 pad。以 Demuxer 元素为例,动态 pad 通常是在媒体解析过程中,当新的数据流被识别后才创建。

为了查看元素的 pad 信息,可以使用 gst-inspect-1.0 工具。例如,查看 audioconvert 元素的 pad 信息,可以发现它有一个静态的 sink pad 和一个静态的 source pad,如下所示:

Pads:
  SINK: 'sink'
    Pad Template: 'sink'
  SRC: 'src'
    Pad Template: 'src'

上面的输出说明 audioconvert 元素在创建时就包含了一个静态的 sink pad 和一个静态的 source pad。也就是说,这些 pad 在元素创建之初就已经存在,并且不会在运行时动态添加或移除。

2.2.3 尝试连接

if (gst_pad_is_linked (sink_pad)) {
  g_print ("We are already linked. Ignoring.\n");
  goto exit;
}

uridecodebin 创建的 pad 数量取决于视频文件,视频文件中存在多个流,它就会创建多个 source pad,因此回调函数也会被调用多次。因此我们先进行判断,如果 audioconvert 的 sink pad 已经被连接了,那么就不在处理。

2.2.4 获取 pad 的能力

每个元素中的 pad 它的能力是不同的,以 audioconvert 举例说明,使用 gst-inspect-1.0 查看 audioconvert 的信息,你可以看到 Pad Templates 的描述,在 GStreamer 中,Pad Templates 是一种描述元素的 pad 及其能力(caps)的结构。它告诉我们一个元素可以创建什么样的 pad(SINK 或 SRC)以及这些 pad 能处理的数据格式是什么。Pad Templates 可以帮助开发者了解如何将不同的元素连接在一起。

Pad Templates:
SINK template: 'sink'
  Availability: Always
  Capabilities:
    audio/x-raw
      format: { (string)F64LE, ..., (string)U8 }
      rate: [ 1, 2147483647 ]
      channels: [ 1, 2147483647 ]
      layout: { (string)interleaved, (string)non-interleaved }
SRC template: 'src'
  Availability: Always
  Capabilities:
    audio/x-raw
      format: { (string)F64LE, ..., (string)U8 }
      rate: [ 1, 2147483647 ]
      channels: [ 1, 2147483647 ]
      layout: { (string)interleaved, (string)non-interleaved }

对于 SINK pad:

SINK template: 'sink'
  Availability: Always
  Capabilities:
    audio/x-raw
      format: { (string)F64LE, ..., (string)U8 }
      rate: [ 1, 2147483647 ]
      channels: [ 1, 2147483647 ]
      layout: { (string)interleaved, (string)non-interleaved }

解释:

  • 'sink' 是 pad 的名称,表示这是一个 SINK pad(用于接受数据)。
  • Availability: Always 表示这个 pad 总是可用。
  • Capabilities 部分描述了该 pad 支持的所有格式,表示这个 pad 可以处理各种不同格式的原始音频(audio/x-raw),支持多种音频格式、采样率、通道数和布局。

对于 SRC pad:

SRC template: 'src'
  Availability: Always
  Capabilities:
    audio/x-raw
      format: { (string)F64LE, ..., (string)U8 }
      rate: [ 1, 2147483647 ]
      channels: [ 1, 2147483647 ]
      layout: { (string)interleaved, (string)non-interleaved }

解释:

  • 'src' 是 pad 的名称,表示这是一个 SRC pad(用于输出数据)。
  • Availability: Always 表示这个 pad 总是可用。
  • Capabilities 部分描述了该 pad 可以输出的所有格式,类似于 SINK pad,支持多种音频格式、采样率、通道数和布局。
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;
}

我们需要检查这个新 pad 输出的数据类型,因为我们只关心音频 pad。我们之前已经创建了一个处理音频的管道(包括 audioconvert、audioresample 和 autoaudiosink),而这个管道无法连接到生成视频的 pad。

使用 gst_pad_get_current_caps() 获取 pad 当前的能力(即输出的数据类型),这些信息被封装在一个 GstCaps 结构中。pad 支持的所有可能能力可以通过 gst_pad_query_caps() 查询。一个 pad 可以提供多种能力,因此 GstCaps 可能包含多个 GstStructure,每个代表一种能力。而当前能力只包含一个 GstStructure,或者为 NULL(如果还没有能力)。

在这种情况下,我们知道目标 pad 仅支持一种能力(音频)。因此,我们用 gst_caps_get_structure() 获取第一个 GstStructure

接着,使用 gst_structure_get_name() 获取结构的名称,它包含了格式的主要描述(即媒体类型)。

如果名称不是 audio/x-raw,则表示这不是一个解码后的音频 pad,我们不需要处理它。

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() 尝试链接两个 pad。与 gst_element_link() 的情况一样,链接必须从源 pad 到接收 pad 指定,并且两个 pad 必须由同一 bin(或 pipeline)中的元素拥有。连接后 pipeline 拓扑结构如下图

在这里插入图片描述

到这里,我们的工作就完成了!当出现合适类型的 pad 时,它会被链接到剩余的音频处理管道,然后执行将继续直到出现 ERROR 或 EOS。但是,我们将通过引入状态(State)的概念,从这个教程中获得更多的内容。

2.3 GStreamer 状态

在 GStreamer 中,状态(State)是一个非常重要的概念,它控制了元素(Element)和管道(Pipeline)的行为和运行状态。GStreamer 提供了四个主要状态以及一些中间状态变化,它们能够帮助管理多媒体数据流的各个阶段。

2.3.1 四个主要状态

  1. NULL

    • 描述: 元素的初始状态。此时,元素没有资源分配,没有打开设备,并且不会处理任何数据。
    • 用途: 通常用于元素的初始化或重置。
  2. READY

    • 描述: 元素已经分配了必要的资源,但还没有开始处理数据。在这个状态下,元素已经准备好,但还没有开始进行播放或处理数据。
    • 用途: 用于元素的准备阶段,确保资源就绪但没有实际启动数据处理。
  3. PAUSED

    • 描述: 元素处于暂停状态,已经开始处理数据但没有进行流动。此状态可用于一些需要在处理过程中暂停数据传输的场景。
    • 用途: 可以用于预览、缓冲和准备跳转到播放状态。同时,这也是播放停止后的状态,可以在恢复播放时继续从此状态播放。
  4. PLAYING

    • 描述: 元素正在实时处理和处理数据,数据流动和处理是在进行中的。此状态下,元素将实际进行数据的处理和播放。
    • 用途: 元素处于活动状态,进行音视频播放或其他数据处理任务。

2.3.2 状态切换

你只能在相邻的状态之间进行切换,这就是说,你不能直接从 NULL 切换到 PLAYING,你需要先经过中间的 READY 和 PAUSED 状态。然而,如果你将 pipeline 设置为 PLAYING 状态,GStreamer 会为你处理中间的状态转换。

case GST_MESSAGE_STATE_CHANGED:
  /* 我们只对来自 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;

我们添加了这段代码来监听总线消息,关于状态改变,并在屏幕上打印它们,以帮助你理解状态转换。每个元素都会将其当前状态的消息放到总线上,所以我们进行过滤,只监听来自 pipeline 的消息。

大多数应用程序只需关注将 pipeline 设为 PLAYING 以开始播放,然后设为 PAUSED 以执行暂停操作,最后在程序退出时设为 NULL 以释放所有资源。

2.4 练习

Base tutorial 3 只能听到声音,添加 autovideosink 和 videoconvert 使其也能看到视频画面。

完成代码参考 basic-tutorial-3-exercise.c

重点代码进行说明

typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *video_convert;
  GstElement *video_sink;
  GstElement *audio_convert;
  GstElement *audio_resample;
  GstElement *audio_sink;
} CustomData;

CustomData 中添加 video_convertvideo_sink 两个元素

	// link audio elements
  if (!gst_element_link_many(data.audio_convert, data.audio_resample, data.audio_sink, NULL)) {
    g_printerr("Audio Elements could not be linked.\n");
    gst_object_unref(data.pipeline);
    return -1;
  }
  
  // link video elements
  if (!gst_element_link_many(data.video_convert, data.video_sink, NULL)) {
    g_printerr("Video Elements could not be linked.\n");
    gst_object_unref(data.pipeline);
    return -1;
  }

除了连接音频处理链路外,也连接视频链路上的 convert 和 sink。

static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data) {
  GstPad *audio_sink_pad = gst_element_get_static_pad (data->audio_convert, "sink");
  GstPad *video_sink_pad = gst_element_get_static_pad (data->video_convert, "sink");

  // ....
  // if not audio pad type, skip
  if (g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    // link audio pad
    ret = gst_pad_link(new_pad, audio_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);
    }
  } else
  {
    // link video pad
    ret = gst_pad_link(new_pad, video_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);
    }
  }
//...

回调函数中,如果创建的 pad 是视频类型,则与 videoconvert 进行连接。Pipeline 的拓扑结构如下图

在这里插入图片描述

参考

  • Basic tutorial 3: Dynamic pipelines
  • 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows11怎么加密?如何对win11系统文件进行加密?

“昔者,有物藏于室,恐人窥之,必施锁钥以护之。今之世,信息如海,数据若金,保护之责,重于泰山。Windows 11,微软之新篇,亦需加密之术,以护数据安全。” 本文将…

Golang | Leetcode Golang题解之第341题扁平化嵌套列表迭代器

题目: 题解: type NestedIterator struct {vals []int }func Constructor(nestedList []*NestedInteger) *NestedIterator {var vals []intvar dfs func([]*NestedInteger)dfs func(nestedList []*NestedInteger) {for _, nest : range nestedList {if…

期末速成复习资料——操作系统

体型:选择20判断10填空10*2简答4*5计算2*10 第一章 在一个计算机系统中,通常都含有多种硬件和软件资源。归纳起来可将这些资源分为四类:处理机、存储器、I/O设备以及文件(数据和程序)。相应地,OS的主要功能…

html中的<base>标签和 href属性的使用

先截图一个菜鸟教程的案例 说白了就是将本页面所有的URL前边都加上base标签后边的href中的内容即:

财务会计与管理会计(八)

文章目录 电商绩效工资计算IF函数的应用 参数表的格式转换INDEX、MATCH函数的应用 巧妙计算计件工资引出问题调整方案增加变量扩大范围 智能值班表VLOOKUP函数的使用方法 按照设备类别自动编号VLOOKUP、COUNTIF、TEXT函数的使用方法 多公司多月份损益汇总表OFFSET、INDIRECT函数…

154 · 正则表达式匹配

链接:LintCode 炼码 - 更高效的学习体验! 题解: class Solution { public:/*** param s: A string * param p: A string includes "." and "*"* return: A boolean*/bool isMatch(string &s, string &p) {// w…

Unity求向量和平面的交点

已知条件:平面P的法向量,平面上的一点P0,直线L的方向向量,直线上的一点L0 公式推导: 主要是两点: 1.目标点T在直线上:TL0D*(D未知) 2.目标点T在平面上,则T…

Kubectl 常用命令汇总大全

kubectl 是 Kubernetes 自带的客户端,可以用它来直接操作 Kubernetes 集群。 从用户角度来说,kubectl 就是控制 Kubernetes 的驾驶舱,它允许你执行所有可能的 Kubernetes 操作;从技术角度来看,kubectl 就是 Kubernetes…

【JavaEE】一文学会如何使用:文件IO操作(详解)

目录 前言 什么是IO? IO流原理 IO流分类 InputStream字节流输入 文件输入流--FileInputStream 理解InputStream中的read方法 关闭文件操作 利用Scanner进行字符获取 OutputStream字节流输出 文件输出流--FileOutputStream 理解OutputStream中的…

计算机毕业设计选题推荐-在线学习平台-Java/Python项目实战

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Linux - 权限

文章目录 一、用户二、文件 一、用户 1、Linux下有两种用户:超级用户(root)、普通用户。 超级用户:可以再linux系统下做任何事情,不受限制 。 普通用户:在linux下做有限的事情。 超级用户的命令提示符是“…

Python数据分析实战:从零开始构建销售预测模型

随着大数据时代的到来,数据分析已经成为企业决策的重要依据之一。Python作为一种强大的编程语言,在数据分析领域有着广泛的应用。本文将通过一个具体的案例——销售预测——来演示如何使用Python进行数据预处理、特征工程、模型构建与评估。无论你是Pyth…

【持续更新】Adoobe Afteer Effeects 2024 v24.5.0.052最新免费修改版

利用Adoobe Afteer Effeects CC,您可以轻松打造生动的动画标题、字幕以及下三分屏元素。无论是从零开始还是借助应用内丰富的预设动画,都能让您的文字以各种方式动起来,无论是旋转、滑动或是平移,创意无限。 将视频和图像融合在一…

负载均衡、高可用

负载均衡 负载均衡(Load Balance):可以利用多个计算机和组合进行海量请求处理,从而获得很高的处理效率,也可以用多个计算机做备份(高可用),使得任何一个机器坏了整个系统还是能正常…

华为HCIP证书好考吗?详解HCIP证书考试难易程度及备考策略!

华为认证体系主要分为三个层次:HCIA(Huawei Certified ICT Associate),HCIP(Huawei Certified ICT Professional)和HCIE(Huawei Certified Internetwork Expert)。作为中级认证,HCIP证书主要面向具备一定技术基础和项目实践能力的专业人士。在…

SQL 数据库设计、事务、视图 <13>

一、数据库设计 1.多表之间的关系 1) 一对一(了解) 如:人和身份证 分析:一个人只有一个身份证,一个身份证只能对应一个人 2)一对多(多对一) 如:部门和员…

【C语言】深入讲解指针(上)

文章目录 前言字符指针指针数组数组指针数组指针的定义&数组名和数组名数组指针的使用 指针和数组传参一维数组传参二维数组传参一级指针传参二级指针传参 结束 前言 之前我们初步了解了指针的概念,没有看过的大家可以移步到【C语言】初阶指针详解,…

手撕C++入门基础

1.C介绍 C课程包括:C语法、STL、高阶数据结构 C参考文档:Reference - C Reference C 参考手册 - cppreference.com cppreference.com C兼容之前学习的C语言 2.C的第一个程序 打印hello world #define _CRT_SECURE_NO_WARNINGS 1 // test.cpp // …

day02--HTML CSS

一、HTML表单 表单的作用是用于采集用户再页面上填入的数据,并发送给后端服务器,经常用于用户注册、登录、xx信息添加、xx信息修改 1.1表单 1、input表示文本框 type属性:负责配置不同的输入框类型 text:普通文本框 password&…

服务器数据恢复—raid5阵列离线硬盘强制上线失败如何恢复数据?

服务器数据恢复环境: 某品牌2850服务器上有一组由6块SCSI硬盘组建的raid5磁盘阵列,上层操作系统为Redhat linuxext3文件系统。 服务器故障&初检: 服务器在运行过程中突然瘫痪,管理员对服务器中的raid进行检查后发现有两块硬盘…