首页 > 其他分享 >三文带你轻松上手鸿蒙的 AI 语音 02-声音文件转文本

三文带你轻松上手鸿蒙的 AI 语音 02-声音文件转文本

时间:2024-12-15 19:21:43浏览次数:10  
标签:02 string AI SpeechRecognizerManager 录音 static 声音文件 let 音频

三文带你轻松上手鸿蒙的 AI 语音 02-声音文件转文本

接上一文

前言

本文主要实现 使用鸿蒙的 AI 语音功能将声音文件识别并转换成文本

实现流程

  1. 利用AudioCapturer 录制声音,生成录音文件
  2. 利用 AI 语音功能,实现识别

image-20240829002516961

两个录音库介绍

HarmonyOS NEXT 应用开中,实现录音的两个核心库分别为

  1. AudioCapturer
  2. AVRecorder

AVRecorder 录制出来的声音封装格式只能是 aac,这个文件格式我们的 AI 语音引擎不支持,AI 语音引擎只支持 pcm 格式,而 AudioCapturer 录制的声音封装格式则是 pcm。因此我们选择使用 AudioCapturer 来录制声音

AudioCapturer

AudioCapturer 介绍

AudioCapturer 是音频采集器,用于录制 PCM(Pulse Code Modulation)音频数据,适合有音频开发经验的开发者实现更灵活的录制功

能。

状态变化示意图

img

能看到使用 AudioCapturer 的主要流程为

  1. 创建 AudioCapturer 实例
  2. 调用 start 方法开始录音
  3. 调用 stop 方法停止录音
  4. 调用 release 方法释放实例

创建 AudioCapturer 实例

文末会提供封装好,可以直接使用的代码 下面的代码示例都是基于封装好的代码进行的

我们通过调用 createAudioCapturer 方法实现创建 AudioCapturer 实例,其中该方法需要传递相关参数。

image-20240829003846034

调用 start 方法开始录音

开始调用 start 方法时,需要准备相关数据。如

  1. 提供录音的文件名,可以自定义
  2. 写入录音数据的回调函数(在录制声音的过程中持续触发)
  3. 调用 start 方法

image-20240829004425443

调用 stop 方法停止录音

调用 stop 方法则相对简单,直接调用即可

image-20240829004829409

调用 release 方法释放实例

同理

image-20240829004910026

封装好的录音代码

\entry\src\main\ets\utils\AudioCapturerManager.ets 下面是这个类的属性和方法的总结:

属性

  • static audioCapturer:
    • 类型是 audio.AudioCapturer | null,是一个静态属性,用于存储当前的音频捕获器实例。
  • private static recordFilePath:
    • 类型是 string,是一个静态私有属性,用于存储录音文件的路径。

方法

  • static async createAudioCapturer():
    • 如果 audioCapturer 已经存在,则直接返回该实例;否则创建一个新的音频捕获器实例,并设置其音频流信息和音频捕获信息,然后创建并返回新的实例。
  • static async startRecord(fileName: string):
    • 异步静态方法,用于启动录音过程。首先调用 createAudioCapturer() 方法确保有一个音频捕获器实例。之后初始化缓冲区大小,并打开或创建一个指定名称的 .wav 录音文件。定义一个读取数据的回调函数,用于将捕获到的数据写入文件中。最后开始录音,并记录下录音文件的路径。
  • static async stopRecord():
    • 异步静态方法,用于停止录音过程。停止音频捕获器的工作,释放其资源,并清除 audioCapturer 实例。
// 导入音频处理模块
import { audio } from "@kit.AudioKit";
// 导入文件系统模块
import fs from "@ohos.file.fs";

// 定义一个管理音频录制的类
export class AudioCapturerManager {
  // 静态属性,用于存储当前的音频捕获器实例
  static audioCapturer: audio.AudioCapturer | null = null;
  // 静态私有属性,用于存储录音文件的路径
  private static recordFilePath: string = "";

  // 静态异步方法,用于创建音频捕获器实例
  static async createAudioCapturer() {
    if (AudioCapturerManager.audioCapturer) {
      return AudioCapturerManager.audioCapturer;
    }
    // 设置音频流信息配置
    let audioStreamInfo: audio.AudioStreamInfo = {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 设置采样率为16kHz
      channels: audio.AudioChannel.CHANNEL_1, // 设置单声道
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 设置样本格式为16位小端
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, // 设置编码类型为原始数据
    };

    // 设置音频捕获信息配置
    let audioCapturerInfo: audio.AudioCapturerInfo = {
      source: audio.SourceType.SOURCE_TYPE_MIC, // 设置麦克风为音频来源
      capturerFlags: 0, // 捕获器标志,此处为默认值
    };

    // 创建音频捕获选项对象
    let audioCapturerOptions: audio.AudioCapturerOptions = {
      streamInfo: audioStreamInfo, // 使用上面定义的音频流信息
      capturerInfo: audioCapturerInfo, // 使用上面定义的音频捕获信息
    };

    // 创建音频捕获器实例
    AudioCapturerManager.audioCapturer = await audio.createAudioCapturer(
      audioCapturerOptions
    );

    // 返回创建的音频捕获器实例
    return AudioCapturerManager.audioCapturer;
  }

  // 静态异步方法,用于启动录音过程
  static async startRecord(fileName: string) {
    await AudioCapturerManager.createAudioCapturer();
    // 初始化缓冲区大小
    let bufferSize: number = 0;

    // 定义一个内部类来设置写入文件时的选项
    class Options {
      offset?: number; // 文件写入位置偏移量
      length?: number; // 写入数据的长度
    }

    // 获取应用的文件目录路径
    let path = getContext().filesDir;

    // 设置录音文件的完整路径
    let filePath = `${path}/${fileName}.wav`;

    // 打开或创建录音文件
    let file = fs.openSync(
      filePath,
      fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
    );

    // 定义一个读取数据的回调函数
    let readDataCallback = (buffer: ArrayBuffer) => {
      // 创建一个写入文件的选项对象
      let options: Options = {
        offset: bufferSize, // 文件当前位置偏移量
        length: buffer.byteLength, // 数据长度
      };
      // 将数据写入文件
      fs.writeSync(file.fd, buffer, options);
      // 更新缓冲区大小
      bufferSize += buffer.byteLength;
    };

    // 给音频捕获器实例注册读取数据的事件监听器
    AudioCapturerManager.audioCapturer?.on("readData", readDataCallback);

    // 开始录音
    AudioCapturerManager.audioCapturer?.start();

    AudioCapturerManager.recordFilePath = filePath;
    // 返回录音文件的路径
    return filePath;
  }

  // 静态异步方法,用于停止录音过程
  static async stopRecord() {
    // 停止音频捕获器的工作
    await AudioCapturerManager.audioCapturer?.stop();
    // 释放音频捕获器的资源
    await AudioCapturerManager.audioCapturer?.release();
    // 清除音频捕获器实例
    AudioCapturerManager.audioCapturer = null;
  }
}

页面中开始录音

image-20240829005514157

可以通过以下路径查看录音文件是否真实生成

/data/app/el2/100/base/你的项目的boundle名称/haps/entry/files

image-20240829005634585

页面代码

Index.ets

import { PermissionManager } from '../utils/permissionMananger'
import { Permissions } from '@kit.AbilityKit'
import SpeechRecognizerManager from '../utils/SpeechRecognizerManager'
import { AudioCapturerManager } from '../utils/AudioCapturerManager'

@Entry
@Component
struct Index {
  @State
  text: string = ""
  fileName: string = ""
  // 1 申请权限
  fn1 = async () => {
    // 准备好需要申请的权限 麦克风权限
    const permissions: Permissions[] = ["ohos.permission.MICROPHONE"]
    // 检查是否拥有权限
    const isPermission = await PermissionManager.checkPermission(permissions)
    if (!isPermission) {
      //   如果没权限,就主动申请
      PermissionManager.requestPermission(permissions)
    }
  }
  // 2 实时语音识别
  fn2 = () => {
    SpeechRecognizerManager.init(res => {
      console.log("实时语音识别", JSON.stringify(res))
      this.text = res.result
    })
  }
  // 3 开始录音
  fn3 = () => {
    this.fileName = Date.now().toString()
    AudioCapturerManager.startRecord(this.fileName)
  }
  // 4 接收录音
  fn4 = () => {
    AudioCapturerManager.stopRecord()
  }

  build() {
    Column({ space: 10 }) {
      Text(this.text)

      Button("申请权限")
        .onClick(this.fn1)
      Button("实时语音识别")
        .onClick(this.fn2)

      Button("开始录音")
        .onClick(this.fn3)
      Button("结束录音")
        .onClick(this.fn4)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

使用 AI 语音功能 实现声音文件转文本

该流程其实和和上一章的实时识别声音功能类似,只是多了一个步骤

  1. 创建 AI 语音引擎
  2. 注册语音监听事件
  3. 开始监听
  4. 读取录音文件

创建 AI 语音引擎

  /**
   * 创建引擎
   */
  private static async createEngine() {
    // 设置创建引擎参数
    SpeechRecognizerManager.asrEngine = await speechRecognizer.createEngine(SpeechRecognizerManager.initParamsInfo)
  }

注册语音监听事件


  /**
   * 设置回调
   */
  private static setListener(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {
  }) {
    // 创建回调对象
    let setListener: speechRecognizer.RecognitionListener = {
      // 开始识别成功回调
      onStart(sessionId: string, eventMessage: string) {
      },
      // 事件回调
      onEvent(sessionId: string, eventCode: number, eventMessage: string) {
      },
      // 识别结果回调,包括中间结果和最终结果
      onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) {
        SpeechRecognizerManager.speechResult = result
        callback && callback(result)
      },
      // 识别完成回调
      onComplete(sessionId: string, eventMessage: string) {
      },
      // 错误回调,错误码通过本方法返回
      // 如:返回错误码1002200006,识别引擎正忙,引擎正在识别中
      // 更多错误码请参考错误码参考
      one rror(sessionId: string, errorCode: number, errorMessage: string) {
        console.log("errorMessage", errorMessage)
      },
    }
    // 设置回调
    SpeechRecognizerManager.asrEngine?.setListener(setListener);
  }

开始监听

需要设置 recognitionMode 为 1 表示识别语音文件

  /**
   * 开始监听
   * */
  static startListening2() {
    try { // 设置开始识别的相关参数
      let recognizerParams: speechRecognizer.StartParams = {
        // 会话id
        sessionId: SpeechRecognizerManager.sessionId,
        // 音频配置信息。
        audioInfo: {
          // 音频类型。 当前仅支持“pcm”
          audioType: 'pcm',
          // 音频的采样率。 当前仅支持16000采样率
          sampleRate: 16000,
          // 音频返回的通道数信息。 当前仅支持通道1。
          soundChannel: 1,
          // 音频返回的采样位数。 当前仅支持16位
          sampleBit: 16
        },
        //   录音识别
        extraParams: {
          // 0:实时录音识别  会自动打开麦克风 录制实时语音
          // 1 识别语音文件
          "recognitionMode": 1,
          //   最大支持音频时长
          maxAudioDuration: 60000
        }
      }
      // 调用开始识别方法
      SpeechRecognizerManager.asrEngine?.startListening(recognizerParams);
    } catch (e) {
      console.log("e", e.code, e.message)
    }
  };

读取录音文件

需要调用 SpeechRecognizerManager.asrEngine?.writeAudio 来监听语音文件

  /**
   *
   * @param fileName {string} 语音文件名称
   */
  private static async writeAudio(fileName: string) {
    let ctx = getContext();
    let filePath: string = `${ctx.filesDir}/${fileName}.wav`;
    let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE);
    let buf: ArrayBuffer = new ArrayBuffer(1280);
    let offset: number = 0;
    while (1280 == fileIo.readSync(file.fd, buf, {
      offset: offset
    })) {
      let uint8Array: Uint8Array = new Uint8Array(buf);
      // 调用AI语音引擎识别
      SpeechRecognizerManager.asrEngine?.writeAudio(SpeechRecognizerManager.sessionId, uint8Array);
      offset = offset + 1280;
    }
    fileIo.closeSync(file);

  }

一步调用

  /**
   * 初始化ai语音转文字引擎
   */
  static async init2(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {
  }, fileName: string) {
    try {
      await SpeechRecognizerManager.createEngine()
      SpeechRecognizerManager.setListener(callback)
      SpeechRecognizerManager.startListening2()
      SpeechRecognizerManager.writeAudio(fileName)
    } catch (e) {
      console.log("e", e.message)
    }
  }

完整代码

import { speechRecognizer } from "@kit.CoreSpeechKit";
import { fileIo } from "@kit.CoreFileKit";

class SpeechRecognizerManager {
  /**
   * 语种信息
   * 语音模式:长
   */
  private static extraParam: Record<string, Object> = {
    locate: "CN",
    recognizerMode: "short",
  };
  private static initParamsInfo: speechRecognizer.CreateEngineParams = {
    /**
     * 地区信息
     * */
    language: "zh-CN",
    /**
     * 离线模式:1
     */
    online: 1,
    extraParams: this.extraParam,
  };
  /**
   * 引擎
   */
  private static asrEngine: speechRecognizer.SpeechRecognitionEngine | null =
    null;
  /**
   * 录音结果
   */
  static speechResult: speechRecognizer.SpeechRecognitionResult | null = null;
  /**
   * 会话ID
   */
  private static sessionId: string = "asr" + Date.now();

  /**
   * 创建引擎
   */
  private static async createEngine() {
    // 设置创建引擎参数
    SpeechRecognizerManager.asrEngine = await speechRecognizer.createEngine(
      SpeechRecognizerManager.initParamsInfo
    );
  }

  /**
   * 设置回调
   */
  private static setListener(
    callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {}
  ) {
    // 创建回调对象
    let setListener: speechRecognizer.RecognitionListener = {
      // 开始识别成功回调
      onStart(sessionId: string, eventMessage: string) {},
      // 事件回调
      onEvent(sessionId: string, eventCode: number, eventMessage: string) {},
      // 识别结果回调,包括中间结果和最终结果
      onResult(
        sessionId: string,
        result: speechRecognizer.SpeechRecognitionResult
      ) {
        SpeechRecognizerManager.speechResult = result;
        callback && callback(result);
      },
      // 识别完成回调
      onComplete(sessionId: string, eventMessage: string) {},
      // 错误回调,错误码通过本方法返回
      // 如:返回错误码1002200006,识别引擎正忙,引擎正在识别中
      // 更多错误码请参考错误码参考
      one rror(sessionId: string, errorCode: number, errorMessage: string) {
        console.log("errorMessage", errorMessage);
      },
    };
    // 设置回调
    SpeechRecognizerManager.asrEngine?.setListener(setListener);
  }

  /**
   * 开始监听
   * */
  static startListening() {
    try {
      // 设置开始识别的相关参数
      let recognizerParams: speechRecognizer.StartParams = {
        // 会话id
        sessionId: SpeechRecognizerManager.sessionId,
        // 音频配置信息。
        audioInfo: {
          // 音频类型。 当前仅支持“pcm”
          audioType: "pcm",
          // 音频的采样率。 当前仅支持16000采样率
          sampleRate: 16000,
          // 音频返回的通道数信息。 当前仅支持通道1。
          soundChannel: 1,
          // 音频返回的采样位数。 当前仅支持16位
          sampleBit: 16,
        },
        //   录音识别
        extraParams: {
          // 0:实时录音识别  会自动打开麦克风 录制实时语音
          recognitionMode: 0,
          //   最大支持音频时长
          maxAudioDuration: 60000,
        },
      };
      // 调用开始识别方法
      SpeechRecognizerManager.asrEngine?.startListening(recognizerParams);
    } catch (e) {
      console.log("e", e.code, e.message);
    }
  }

  /**
   *
   * @param fileName {string} 语音文件名称
   */
  private static async writeAudio(fileName: string) {
    let ctx = getContext();
    let filePath: string = `${ctx.filesDir}/${fileName}.wav`;
    let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE);
    let buf: ArrayBuffer = new ArrayBuffer(1280);
    let offset: number = 0;
    while (
      1280 ==
      fileIo.readSync(file.fd, buf, {
        offset: offset,
      })
    ) {
      let uint8Array: Uint8Array = new Uint8Array(buf);
      // 调用AI语音引擎识别
      SpeechRecognizerManager.asrEngine?.writeAudio(
        SpeechRecognizerManager.sessionId,
        uint8Array
      );
      // 延迟40ms
      SpeechRecognizerManager.sleep(40);
      offset = offset + 1280;
    }
    fileIo.closeSync(file);
  }

  static sleep(time: number) {
    return Promise<null>((resolve: Function) => {
      setTimeout(() => {
        resolve();
      }, 40);
    });
  }

  /**
   * 开始监听
   * */
  static startListening2() {
    try {
      // 设置开始识别的相关参数
      let recognizerParams: speechRecognizer.StartParams = {
        // 会话id
        sessionId: SpeechRecognizerManager.sessionId,
        // 音频配置信息。
        audioInfo: {
          // 音频类型。 当前仅支持“pcm”
          audioType: "pcm",
          // 音频的采样率。 当前仅支持16000采样率
          sampleRate: 16000,
          // 音频返回的通道数信息。 当前仅支持通道1。
          soundChannel: 1,
          // 音频返回的采样位数。 当前仅支持16位
          sampleBit: 16,
        },
        //   录音识别
        extraParams: {
          // 0:实时录音识别  会自动打开麦克风 录制实时语音
          // 1 识别语音文件
          recognitionMode: 1,
          //   最大支持音频时长
          maxAudioDuration: 60000,
        },
      };
      // 调用开始识别方法
      SpeechRecognizerManager.asrEngine?.startListening(recognizerParams);
    } catch (e) {
      console.log("e", e.code, e.message);
    }
  }

  /**
   * 取消识别
   */
  static cancel() {
    SpeechRecognizerManager.asrEngine?.cancel(
      SpeechRecognizerManager.sessionId
    );
  }

  /**
   * 释放ai语音转文字引擎
   */
  static shutDown() {
    SpeechRecognizerManager.asrEngine?.shutdown();
  }

  /**
   * 停止并且释放资源
   */
  static async release() {
    SpeechRecognizerManager.cancel();
    SpeechRecognizerManager.shutDown();
  }

  /**
   * 初始化ai语音转文字引擎
   */
  static async init(
    callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => {}
  ) {
    await SpeechRecognizerManager.createEngine();
    SpeechRecognizerManager.setListener(callback);
    SpeechRecognizerManager.startListening();
  }

  /**
   * 初始化ai语音转文字引擎
   */
  static async init2(
    callback: (
      srr: speechRecognizer.SpeechRecognitionResult
    ) => void = () => {},
    fileName: string
  ) {
    try {
      await SpeechRecognizerManager.createEngine();
      SpeechRecognizerManager.setListener(callback);
      SpeechRecognizerManager.startListening2();
      SpeechRecognizerManager.writeAudio(fileName);
    } catch (e) {
      console.log("e", e.message);
    }
  }
}

export default SpeechRecognizerManager;

页面代码

image-20240920104405824

import { PermissionManager } from '../utils/permissionMananger'
import { Permissions } from '@kit.AbilityKit'
import SpeechRecognizerManager from '../utils/SpeechRecognizerManager'
import { AudioCapturerManager } from '../utils/AudioCapturerManager'
import TextToSpeechManager from '../utils/TextToSpeechManager'

@Entry
@Component
struct Index {
  @State
  text: string = ""
  fileName: string = ""
  // 1 申请权限
  fn1 = async () => {
    // 准备好需要申请的权限 麦克风权限
    const permissions: Permissions[] = ["ohos.permission.MICROPHONE"]
    // 检查是否拥有权限
    const isPermission = await PermissionManager.checkPermission(permissions)
    if (!isPermission) {
      //   如果没权限,就主动申请
      PermissionManager.requestPermission(permissions)
    }
  }
  // 2 实时语音识别
  fn2 = () => {
    SpeechRecognizerManager.init(res => {
      console.log("实时语音识别", JSON.stringify(res))
      this.text = res.result
    })
  }
  // 3 开始录音
  fn3 = () => {
    this.fileName = Date.now().toString()
    AudioCapturerManager.startRecord(this.fileName)
  }
  // 4 接收录音
  fn4 = () => {
    AudioCapturerManager.stopRecord()
  }
  // 5 声音文件转换文本
  fn5 = () => {
    SpeechRecognizerManager.init2(res => {
      this.text = res.result
      console.log("声音文件转换文本", JSON.stringify(res))
    }, this.fileName)
  }
  // 6 文本合成声音
  fn6 = async () => {
    const tts = new TextToSpeechManager()
    await tts.createEngine()
    tts.setListener((res) => {
      console.log("res", JSON.stringify(res))
    })
    tts.speak("我送你离开 千里之外")
  }

  build() {
    Column({ space: 10 }) {
      Text(this.text)

      Button("申请权限")
        .onClick(this.fn1)
      Button("实时语音识别")
        .onClick(this.fn2)

      Button("开始录音")
        .onClick(this.fn3)
      Button("结束录音")
        .onClick(this.fn4)

      Button("声音文件转换文本")
        .onClick(this.fn5)
      Button("文本合成声音")
        .onClick(this.fn6)

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

标签:02,string,AI,SpeechRecognizerManager,录音,static,声音文件,let,音频
From: https://www.cnblogs.com/aspXiaoBai/p/18608347

相关文章

  • 三文带你轻松上手鸿蒙的 AI 语音 01-实时语音识别
    三文带你轻松上手鸿蒙的AI语音01-实时语音识别前言HarmonyOSNext中集成了强大的AI功能。CoreSpeechKit(基础语音服务)是它提供的众多AI功能中的一种。CoreSpeechKit(基础语音服务)集成了语音类基础AI能力,包括文本转语音(TextToSpeech)及语音识别(SpeechRecognizer)能力,便......
  • 2024-2025-1 20241423 《计算机基础与程序设计》第十二周学习总结
    作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计(https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP)这个作业要求在哪里2024-2025-1计算机基础与程序设计第十二周作业(https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13276)......
  • 【最新】北大数字普惠金融指数数据集-省市县(2011-2023年)
    一、数据介绍数据名称:第六期北大数字普惠金融指数-省市县数据年份:2011-2023年数据范围:全国31个省、337个地级以上城市以及2800个县数据说明:编制方法请参阅《经济学(季刊)》中的《测度中国数字普惠金融发展:指数编制与空间特征》时间跨度:省级和城市级指数时间跨度为2011-202......
  • 揭示NVIDIA成功背后的关键因素,并探讨其未来在AI领域的潜力
    揭示NVIDIA成功背后的关键因素,并探讨其未来在AI领域的潜力NVIDIA的成功背后有几个关键因素,这些因素不仅巩固了其在当前市场的领导地位,也为未来在人工智能(AI)领域的发展奠定了坚实的基础。以下是对此的详细探讨:1.硬件和软件的深度整合NVIDIA的核心竞争力体现在其硬件(如GPU......
  • 2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest
    自己vp了一下这一场,赛时7题,比较简单,但是有几题也是卡了蛮久。都是思维题。C感觉结论比较显然但是实现上被卡住了。用map没过,重构的时候把多个数压缩成一个数处理ac了,对拍发现是因为循环逻辑导致错误了。。#include<bits/stdc++.h>usingnamespacestd;#defineLLlonglon......
  • 鸿蒙动画开发02——组件内转场动画
    我们接着鸿蒙动画开发01——布局更新动画,现在了解鸿蒙的组件内部的转场动画。1、概述组件的插入、删除过程即为组件本身的转场过程,组件的插入、删除动画称为组件内转场动画。通过组件内转场动画,可定义组件出现、消失的效果。2、关键接口组件内转场动画的接口为:transition(val......
  • 2024-2025-1 20241314 《计算机基础与程序设计》第十二周学习总结
    2024-2025-120241314《计算机基础与程序设计》第十二周学习总结作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计这个作业要求在哪里2024-2025-1计算机基础与程序设计第十二周作业这个作业的目标复习作业正文正文教材学习内容总结引......
  • PyQt5 使用结合Logging 在 QPlainTextEdit/QTextBrowser 上显示日志信息
    PyQt5使用结合Logging在QPlainTextEdit/QTextBrowser上显示日志信息本文演示PyQt5如何与Python的标准库Logging结合,然后输出日志信息到如:QPlainTextEditQTextBrowser上代码结构本文中全部代码全在test_QPlainTextEdit_Log.py这一个文件中编码,步骤中有变动的地方会注......
  • Windows Server 2022 DHCP 服务器 是一种网络服务,属于 动态主机配置协议(DHCP, Dynamic
    WindowsServer2022DHCP服务器是一种网络服务,属于动态主机配置协议(DHCP,DynamicHostConfigurationProtocol)实现的一部分,用于自动为网络中的计算机和设备分配IP地址和其他网络配置信息,如子网掩码、默认网关和DNS服务器等。功能和作用:自动化IP地址分配:DHCP服......
  • Apache Struts 远程代码执行漏洞(CVE-2024-53677)
       0x01阅读须知        技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均......