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

iOS解码实现

import Foundation
import VideoToolboxclass KFVideoDecoderInputPacket {var sampleBuffer: CMSampleBuffer?
}class KFVideoDecoder {// MARK: - 常量private let kDecoderRetrySessionMaxCount = 5private let kDecoderDecodeFrameFailedMaxCount = 20// MARK: - 回调var pixelBufferOutputCallBack: ((CVPixelBuffer, CMTime) -> Void)?var errorCallBack: ((Error) -> Void)?// MARK: - 属性private var decoderSession: VTDecompressionSession? // 视频解码器实例private let decoderQueue: DispatchQueueprivate let semaphore: DispatchSemaphoreprivate var retrySessionCount: Int = 0 // 解码器重试次数private var decodeFrameFailedCount: Int = 0 // 解码失败次数private var gopList: [KFVideoDecoderInputPacket] = []private var inputCount: Int = 0 // 输入帧数var outputCount: Int = 0 // 输出帧数// MARK: - 生命周期init() {decoderQueue = DispatchQueue(label: "com.KeyFrameKit.videoDecoder", qos: .default)semaphore = DispatchSemaphore(value: 1)gopList = []}deinit {semaphore.wait()releaseDecompressionSession()clearCompressQueue()semaphore.signal()}// MARK: - 公共方法func decodeSampleBuffer(_ sampleBuffer: CMSampleBuffer) {guard CMSampleBufferIsValid(sampleBuffer),retrySessionCount < kDecoderRetrySessionMaxCount,decodeFrameFailedCount < kDecoderDecodeFrameFailedMaxCount else {return}// 为异步操作保留样本缓冲区let unmanagedSampleBuffer = Unmanaged.passRetained(sampleBuffer)decoderQueue.async { [weak self] inguard let self = self else {unmanagedSampleBuffer.release()return}self.semaphore.wait()// 1、如果还未创建解码器实例,则创建解码器var setupStatus = noErrif self.decoderSession == nil {if let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) {setupStatus = self.setupDecompressionSession(videoDescription: formatDescription)self.retrySessionCount = (setupStatus == noErr) ? 0 : (self.retrySessionCount + 1)if setupStatus != noErr {self.releaseDecompressionSession()}}}if self.decoderSession == nil {unmanagedSampleBuffer.release()self.semaphore.signal()if self.retrySessionCount >= self.kDecoderRetrySessionMaxCount, let errorCallback = self.errorCallBack {DispatchQueue.main.async {let error = NSError(domain: String(describing: KFVideoDecoder.self), code: Int(setupStatus), userInfo: nil)errorCallback(error)}}return}// 2、对 sampleBuffer 进行解码var flags = VTDecodeFrameFlags._EnableAsynchronousDecompressionvar flagsOut = VTDecodeInfoFlags()var decodeStatus = VTDecompressionSessionDecodeFrame(self.decoderSession!,sampleBuffer: sampleBuffer,flags: flags,frameRefcon: nil,infoFlagsOut: &flagsOut)if decodeStatus == kVTInvalidSessionErr {// 解码当前帧失败,进行重建解码器重试self.releaseDecompressionSession()if let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) {setupStatus = self.setupDecompressionSession(videoDescription: formatDescription)self.retrySessionCount = (setupStatus == noErr) ? 0 : (self.retrySessionCount + 1)if setupStatus == noErr {// 重建解码器成功后,要从当前 GOP 开始的 I 帧解码flags = ._DoNotOutputFramefor packet in self.gopList {if let packetBuffer = packet.sampleBuffer {_ = VTDecompressionSessionDecodeFrame(self.decoderSession!,sampleBuffer: packetBuffer,flags: flags,frameRefcon: nil,infoFlagsOut: &flagsOut)}}// 解码当前帧flags = ._EnableAsynchronousDecompressiondecodeStatus = VTDecompressionSessionDecodeFrame(self.decoderSession!,sampleBuffer: sampleBuffer,flags: flags,frameRefcon: nil,infoFlagsOut: &flagsOut)} else {// 重建解码器失败self.releaseDecompressionSession()}}} else if decodeStatus != noErr {print("KFVideoDecoder 解码错误: \(decodeStatus)")}// 统计解码入帧数self.inputCount += 1// 遇到新的 I 帧后,清空上一个 GOP 序列缓存if self.isKeyFrame(sampleBuffer: sampleBuffer) {self.clearCompressQueue()}// 存储当前帧到 GOP 列表let packet = KFVideoDecoderInputPacket()packet.sampleBuffer = unmanagedSampleBuffer.takeRetainedValue()self.gopList.append(packet)// 记录解码失败次数self.decodeFrameFailedCount = (decodeStatus == noErr) ? 0 : (self.decodeFrameFailedCount + 1)self.semaphore.signal()// 解码失败次数超过上限,报错if self.decodeFrameFailedCount >= self.kDecoderDecodeFrameFailedMaxCount, let errorCallback = self.errorCallBack {DispatchQueue.main.async {let error = NSError(domain: String(describing: KFVideoDecoder.self), code: Int(decodeStatus), userInfo: nil)errorCallback(error)}}}}func flush() {decoderQueue.async { [weak self] inguard let self = self else { return }self.semaphore.wait()self.flushInternal()self.semaphore.signal()}}func flush(completionHandler: @escaping () -> Void) {decoderQueue.async { [weak self] inguard let self = self else {completionHandler()return}self.semaphore.wait()self.flushInternal()self.semaphore.signal()completionHandler()}}// MARK: - 私有方法private func setupDecompressionSession(videoDescription: CMFormatDescription) -> OSStatus {if decoderSession != nil {return noErr}// 1、设置颜色格式let attrs: [String: Any] = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]// 2、设置解码回调var outputCallbackRecord = VTDecompressionOutputCallbackRecord()outputCallbackRecord.decompressionOutputCallback = decompressionOutputCallbackoutputCallbackRecord.decompressionOutputRefCon = Unmanaged.passUnretained(self).toOpaque()// 3、创建解码器实例var session: VTDecompressionSession?let status = VTDecompressionSessionCreate(allocator: kCFAllocatorDefault,formatDescription: videoDescription,decoderSpecification: nil,imageBufferAttributes: attrs as CFDictionary,outputCallback: &outputCallbackRecord,decompressionSessionOut: &session)if status == noErr {decoderSession = session}return status}private func releaseDecompressionSession() {if let session = decoderSession {VTDecompressionSessionWaitForAsynchronousFrames(session)VTDecompressionSessionInvalidate(session)decoderSession = nil}}private func flushInternal() {if let session = decoderSession {VTDecompressionSessionFinishDelayedFrames(session)VTDecompressionSessionWaitForAsynchronousFrames(session)}}private func clearCompressQueue() {gopList.removeAll()}private func isKeyFrame(sampleBuffer: CMSampleBuffer) -> Bool {guard let attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: true) as? [[CFString: Any]],let attachment = attachments.first else {return false}return !attachment.keys.contains(kCMSampleAttachmentKey_NotSync)}
}// MARK: - 回调函数
private func decompressionOutputCallback(decompressionOutputRefCon: UnsafeMutableRawPointer?,sourceFrameRefCon: UnsafeMutableRawPointer?,status: OSStatus,infoFlags: VTDecodeInfoFlags,imageBuffer: CVImageBuffer?,presentationTimeStamp: CMTime,presentationDuration: CMTime)
{guard status == noErr else { return }if infoFlags.contains(.frameDropped) {print("KFVideoDecoder 丢弃帧")return}let decoderOptional = decompressionOutputRefCon.map { Unmanaged<KFVideoDecoder>.fromOpaque($0).takeUnretainedValue() }guard let decoder = decoderOptional,let pixelBuffer = imageBuffer else {return}if let callback = decoder.pixelBufferOutputCallBack {callback(pixelBuffer, presentationTimeStamp)decoder.outputCount += 1}
}
  • 初始化阶段

    • KFVideoDecoderViewControllerviewDidLoad 中初始化解封装器和解码器
    • 设置UI界面,添加"Start"按钮
  • 启动解封装

    • 用户点击"Start"按钮触发 start() 方法
    • 调用 demuxer.startReading,开始读取MP4文件
  • 解封装和解码

    • 解封装成功后,在 fetchAndDecodeDemuxedData 方法中:
      • 循环从 demuxer 获取视频和音频Sample Buffer
      • 将视频Sample Buffer传递给 decoder.decodeSampleBuffer 进行解码
      • 解码器内部使用VideoToolbox的 VTDecompressionSessionDecodeFrame 进行硬件解码
  • 解码回调处理

    • 解码成功后,通过回调函数 decompressionOutputCallback 传递解码后的像素缓冲区
    • 回调触发 pixelBufferOutputCallBack,由 KFVideoDecoderViewController 处理解码后的帧
    • 解码帧被保存到YUV文件中
  • 解码结束

    • demuxer.demuxerStatus.completed 时,表示解封装完成
    • 调用 decoder.flush() 确保所有帧都被处理
    • 将剩余YUV数据写入文件

六、关键技术点

  1. GOP管理
    视频解码需要关键帧作为解码起点。项目中通过gopList存储当前GOP的所有帧,当解码会话失效需要重建时,可以从GOP的I帧开始重新提交解码,避免画面丢失。
  2. 线程安全
    使用专用队列和信号量确保解码操作的线程安全:
    decoderQueue:确保所有解码操作在同一线程序列执行
    semaphore:确保关键资源操作的互斥访问
  3. 异步解码
    VideoToolbox的解码是异步进行的,通过回调函数机制返回结果:
    提交解码任务时设置_EnableAsynchronousDecompression标志开启异步
    解码完成后由VideoToolbox调用我们的回调函数
    回调函数中处理解码结果并传递给上层应用
  4. 错误处理和恢复机制
    解码器实现了健壮的错误处理和恢复机制:
    解码会话失效自动重建
    基于GOP的解码恢复策略
    失败次数限制和错误上报

在这里插入图片描述
需要注意的是,因为是把mp4文件解封装为h264,按照理论来说应该只用处理视频轨道,但是解封装内部的判断为,如果是一个mp4的音视频混合文件,那么视频和音频轨道需要都处理,不然解封装器的状态不能正确结束。

        while let reader = demuxReader, reader.status == .reading && (shouldContinueLoadingAudio || shouldContinueLoadingVideo) {loadCount += 1if loadCount > 100 {break  // 防止无限循环}// 加载音频数据if shouldContinueLoadingAudio {audioQueueSemaphore.wait()let audioCount = CMSimpleQueueGetCount(audioQueue)audioQueueSemaphore.signal()if audioCount < KFMP4DemuxerQueueMaxCount, let audioOutput = readerAudioOutput {// 从音频输出源读取音频数据if let next = audioOutput.copyNextSampleBuffer() {if CMSampleBufferGetDataBuffer(next) == nil {// 移除了CFRelease调用} else {// 将数据从音频输出源 readerAudioOutput 拷贝到缓冲队列 audioQueue 中lastAudioCopyNextTime = CMSampleBufferGetPresentationTimeStamp(next)audioQueueSemaphore.wait()let unmanagedSample = Unmanaged.passRetained(next)CMSimpleQueueEnqueue(audioQueue, element: unmanagedSample.toOpaque())let newAudioCount = CMSimpleQueueGetCount(audioQueue)audioQueueSemaphore.signal()}} else {audioEOF = reader.status == .reading || reader.status == .completedshouldContinueLoadingAudio = false}} else {shouldContinueLoadingAudio = false}}// 加载视频数据if shouldContinueLoadingVideo {videoQueueSemaphore.wait()let videoCount = CMSimpleQueueGetCount(videoQueue)videoQueueSemaphore.signal()if videoCount < KFMP4DemuxerQueueMaxCount, let videoOutput = readerVideoOutput {// 从视频输出源读取视频数据if let next = videoOutput.copyNextSampleBuffer() {if CMSampleBufferGetDataBuffer(next) == nil {// 移除了CFRelease调用} else {// 将数据从视频输出源 readerVideoOutput 拷贝到缓冲队列 videoQueue 中lastVideoCopyNextTime = CMSampleBufferGetDecodeTimeStamp(next)videoQueueSemaphore.wait()let unmanagedSample = Unmanaged.passRetained(next)CMSimpleQueueEnqueue(videoQueue, element: unmanagedSample.toOpaque())let newVideoCount = CMSimpleQueueGetCount(videoQueue)videoQueueSemaphore.signal()}} else {videoEOF = reader.status == .reading || reader.status == .completedshouldContinueLoadingVideo = false// 添加日志,记录视频EOF的设置print("视频EOF标记设置为: \(videoEOF), reader状态: \(reader.status.rawValue)")// 如果视频EOF且没有更多数据,设置demuxer状态为完成if videoEOF && !hasAudioTrack {print("视频处理完成,设置demuxer状态为completed")demuxerStatus = .completed}}} else {shouldContinueLoadingVideo = false}}}// 在函数末尾添加,检查解码是否完成if (audioEOF || !hasAudioTrack) && (videoEOF || !hasVideoTrack) {if demuxerStatus == .running {print("音频和视频均已处理完毕,设置解封装状态为completed")demuxerStatus = .completed}}

我们可以看到,当存在音频和视频轨道的时候,两个EOF必须都变为1,解封装器的状态才会进行改变。

相关文章:

iOS解码实现

import Foundation import VideoToolboxclass KFVideoDecoderInputPacket {var sampleBuffer: CMSampleBuffer? }class KFVideoDecoder {// MARK: - 常量private let kDecoderRetrySessionMaxCount 5private let kDecoderDecodeFrameFailedMaxCount 20// MARK: - 回调var pi…...

Windows中PDF TXT Excel Word PPT等Office文件在预览窗格无法预览的终级解决方法大全

Windows中PDF TXT Excel Word PPT等Office文件在预览窗格无法预览的终级解决方法大全 参考链接&#xff1a; https://zhuanlan.zhihu.com/p/454259765...

在Excel中使用函数公式时,常见错误对应不同的典型问题

在Excel中使用函数公式时&#xff0c;常见错误对应不同的典型问题 1. #DIV/0!&#xff08;除以零错误&#xff09;2. #N/A&#xff08;值不可用&#xff09;3. #NAME?&#xff08;名称错误&#xff09;4. #NULL!&#xff08;空交集错误&#xff09;5. #NUM!&#xff08;数值错…...

Excel

1.快捷键 CtrlE 快速填充 CtrlQ 快速分析 CtrlEnter 原位填充 Tab 横向移动到下一个单元格 Enter 移动到下一行起始位置对应单元格 Shift 返回上一个单元格 0空格分数 显示分数 1.if if(condition,true,false)if(A1>10,"true","fa…...

Rust 学习笔记:错误处理

Rust 学习笔记&#xff1a;错误处理 Rust 学习笔记&#xff1a;错误处理不可恢复的错误带有结果的可恢复错误匹配不同的错误出现错误时 panic 的快捷方式&#xff1a;unwrap 和 expect传播错误传播错误的快捷方式&#xff1a;? 操作符哪里可以使用 ? 操作符 panic or not pan…...

【Linux】系统指令与开发全栈(vim、ssh、gcc)

【Linux】系统指令与开发全栈&#xff08;vim、ssh、gcc&#xff09; 一、Linux 系统指令大全 1、文件与目录管理 基础操作 指令参数说明典型用例注意事项cd~ 家目录&#xff0c;- 返回上级&#xff0c;.. 上级目录cd ~/Documents 进入文档目录无目录权限时会报错ls-l 详情&am…...

用 CodeBuddy 搭建「MiniGoal 小目标打卡器」:一次流畅的 UniApp 开发体验

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 在日常生活中&#xff0c;我们总是希望能够坚持一些小习惯&#xff0c;比如每天锻炼十分钟、读一页书、早睡十分…...

前端(vue)学习笔记(CLASS 6):路由进阶

1、路由的封装抽离 将之前写在main.js文件中的路由配置与规则抽离出来&#xff0c;放置在router/index.js文件中&#xff0c;再将其导入回main.js文件中&#xff0c;即可实现路由的封装抽离 例如 //index.js import { createMemoryHistory, createRouter } from vue-routerim…...

ubuntu 安装 Redis新版Redis 7.x

以下是在Ubuntu系统中安装Redis的详细指南&#xff0c; 一、官方APT源安装 sudo apt install redis-server -y 默认安装最新APT源版本&#xff08;Ubuntu 22.04通常为Redis 6.x&#xff09; 服务自动启动&#xff0c;配置文件路径&#xff1a;/etc/redis/redis.conf验证安装 …...

Httphelper: Http请求webapi小记

文章目录 1、HttpHelper.cs Framework4.812、HttpHelper.cs NET83、JsonHelper.cs Framework4.814、JsonHelper.cs NET85、uniapp request.js 访问WEBAPI 每次查找、测试都比较费事&#xff0c;记录一下把 1、HttpHelper.cs Framework4.81 using System; using System.IO; usi…...

【Linux】进程控制(进程创建、进程终止、进程等待、进程替换)

目录 一、进程创建 1、fork函数 2、页表权限 二、进程终止 1、main函数返回值&#xff08;退出码&#xff09; 2、常见错误码及其对应的错误描述&#xff1a; 将错误退出码转化为错误描述的方法&#xff1a; 3、进程退出的三种场景 4、由上我们可以知道&#xff1a; 5…...

java+selenium专题->启动浏览器下篇

1.简介 上一篇文章&#xff0c;我们已经在搭建的java项目环境中实践了&#xff0c;今天就在基于maven项目的环境中演示一下。 2.eclipse中新建maven项目 1.依次点击eclipse的file - new - other &#xff0c;如下图所示&#xff1a; 2.在搜索框输入关键字“maven”&#xff…...

sqlserver 循环删除1000行

在SQL Server中&#xff0c;如果你想循环删除1000行数据&#xff0c;有几种方法可以实现&#xff0c;但值得注意的是&#xff0c;频繁使用循环删除操作可能会对数据库性能造成影响&#xff0c;尤其是在处理大量数据时。下面介绍几种方法&#xff0c;并讨论它们的优缺点。 方法…...

亚信电子与联发科技携手打造AIoT新未来

[台湾新竹讯, 2025年5月19日] 智能物联网&#xff08;AIoT&#xff09;融合人工智能与物联网技术&#xff0c;通过边缘AI的实时数据分析及设备智能联网能力&#xff0c;加速智能物联网创新应用的蓬勃发展。为满足AIoT产业对多网络端口的应用需求&#xff0c;全球半导体公司【联…...

【成品设计】基于STM32的人体健康监测系统

《基于STM32的人体健康监测系统》 Ps:有4个版本。 V1硬件设计&#xff1a; 主控&#xff1a;STM32F103C8T6&#xff1a;作为系统主控芯片。 血氧心率传感器&#xff1a;用于采集当前心率、血氧值。 温湿度传感器&#xff1a;用于采集当前环境温湿度。 有源低电平触发蜂鸣器&…...

【MySQL进阶】了解linux操作系统下mysql的配置文件和常用选项

前言 &#x1f31f;&#x1f31f;本期讲解关于linux下mysql配置选项的详细介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么…...

LeetCode 219.存在重复元素 II

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 核心思路&#xff1a; 思路详解&#xff1a; 代码&#xff1a; C代码&#xff1a; Java代码&#xff1a; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 219. 存…...

解释:神经网络

在过去的10年里&#xff0c;表现最好的artificial-intelligence系统——比如智能手机上的语音识别器或谷歌最新的自动翻译——都是由一种叫做“深度学习”的技术产生的 深度学习实际上是一种被称为神经网络的人工智能方法的新名称&#xff0c;这种方法已经流行了70多年。1944年…...

Java 泛型详解

在 Java 的类型系统中&#xff0c;泛型&#xff08;Generics&#xff09; 是一个非常重要的特性。它让我们能够编写更通用、更安全的代码&#xff0c;尤其是在处理集合类&#xff08;如 List、Map 等&#xff09;时&#xff0c;泛型的使用可以大大减少类型转换的麻烦&#xff0…...

React集成百度【JSAPI Three】教程(001):快速入门

文章目录 1、快速入门1.1 创建react项目1.2 安装与配置1.3 静态资源配置1.4 配置百度地图AK1.5 第一个DEMO1、快速入门 JSAPI Three版本是一套基于Three.js的三维数字孪生版本地图服务引擎,一套引擎即可支持2D、2.5D、3D全能力的地理投影与数据源加载,帮助开发者轻松搞定平面…...

WPF中资源(Resource)与嵌入的资源(Embedded Resource)的区别及使用场景详解

🌟 开发WPF项目时图片、SVG、配置文件等到底该设置为哪种资源?如何正确读取、跨程序集访问?一篇文章全解答。 在使用 WPF 进行项目开发时,很多开发者在设置文件“生成操作(Build Action)”时,常常会在“资源(Resource)”和“嵌入的资源(Embedded Resource)”之间感…...

如何在 Windows 11 或 10 上安装 Fliqlo 时钟屏保

了解如何在 Windows 11 或 10 上安装 Fliqlo,为您的 PC 或笔记本电脑屏幕添加一个翻转时钟屏保以显示时间。 Fliqlo 是一款适用于 Windows 和 macOS 平台的免费时钟屏保。它也适用于移动设备,但仅限于 iPhone 和 iPad。Fliqlo 的主要功能是在用户不活动时在 PC 或笔记本电脑…...

【STM32】ST-Link V2.1制作

一、下载烧写工具及程序 下载器制作&#xff08;ST-Link V2.1&#xff09; 链接: 提取码&#xff1a;6666https://pan.baidu.com/s/1n0RYNDEw5mBT_CsTFoqrIg?pwd6666 二、安装STM32 CubeProgrammer 双击安装包&#xff0c;点击Next 继续点击Next 选择安装路径&#xff0c;再…...

day30python打卡

知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 一、导入官方库 我们复盘下学习py…...

AI大语言模型评测体系演进与未来展望

随着人工智能技术的飞速发展,大语言模型(LLMs)已成为自然语言处理领域的核心研究方向。2025年最新行业报告显示,当前主流模型的评测体系已从单一任务评估转向多维度、全链路的能力剖析。例如,《全球首个大语言模型意识水平”识商”白盒DIKWP测评报告》通过数据、信息、知识…...

用Python将 PDF 中的表格提取为 Excel/CSV

*用Python将 PDF 中的表格提取为 Excel/CSV&#xff0c;*支持文本型 PDF 和 扫描件/图片型 PDF&#xff08;需 OCR 识别&#xff09;。程序包含以下功能&#xff1a; 1.自动检测 PDF 类型&#xff08;文本 or 扫描件&#xff09; 2.提取表格数据并保存为 Excel/CSV 3.处理多页…...

【工具】ncdu工具安装与使用指南:高效管理Linux磁盘空间

磁盘空间管理是Linux系统维护中的关键任务。当系统提示"磁盘空间不足"时&#xff0c;快速找出占用大量空间的文件和目录变得尤为重要。虽然传统的du命令可以完成这项工作&#xff0c;但其输出往往难以阅读和分析。本文介绍的ncdu&#xff08;NCurses Disk Usage&…...

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Progress Steps (步骤条)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— Progress Steps 组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ ✨ 组件目标 展示一个多步骤的进度条&#xff0c;指示当前所…...

数据分析—Excel数据清洗函数

在做数据分析的过程中&#xff0c;我们从数据库或者网页中获取的外部数据&#xff0c;通常是无法直接使用进行数据分析的。数据经常会有尾随的空格、奇奇怪怪的前缀和非打印字符等等问题&#xff0c;那么我们就需要先对数据进行清洗。下面介绍一些在数据清洗过程中常用的Excel函…...

CEF源码历史版本编译避坑指南

cef编译&#xff0c;网上查到的相关资料大多是官网上自动化编译的翻版&#xff0c;可能较新的版本按照那个步骤编译是没问题的。但是&#xff0c;对于历史版本的编译就会遇到各种坑。步骤大同小异&#xff0c;所以不再赘述&#xff0c;重点记录下针对历史版本编译要注意的点&am…...

看之前熟悉双亲委派加载机制,看之后了解双亲委派加载机制

今天面试被拷打双亲委派加载机制了&#xff0c;麻了。 首先要介绍双亲委派加载机制&#xff0c;就需要先搞明白啥是Java的类加载机制。 一.介绍 Java虚拟机&#xff08;JVM&#xff09;作为Java语言的核心运行环境&#xff0c;承担着将Java字节码转换为机器码并执行的重任。…...

std::ranges::views::stride 和 std::ranges::stride_view

std::ranges::views::stride 是 C23 中引入的一个范围适配器&#xff0c;用于创建一个视图&#xff0c;该视图只包含原始范围中每隔 N 个元素的元素&#xff08;即步长为 N 的元素&#xff09;。 基本概念 std::ranges::stride_view 是一个范围适配器&#xff0c;接受一个输…...

IBM Spectrum Scale (GPFS) 日常运维命令大全

目录 1. 集群管理命令 1.1 集群启动与停止 1.2 节点管理 1.3 集群配置查看与修改 2. 文件系统管理 2.1 文件系统创建与删除 2.2 文件系统挂载与卸载 2.3 文件系统属性修改 3. 存储池与磁盘管理 3.1 存储池管理 3.2 物理磁盘管理 3.3 磁盘故障处理 4. 性能监控与调优…...

IDE 使用技巧与插件推荐

在现代软件开发中&#xff0c;集成开发环境&#xff08;IDE&#xff09;不仅是代码编辑器&#xff0c;更是提升开发效率和代码质量的强大平台。本文将从基础使用技巧、高级功能、插件生态、定制化配置及实战案例五大方面&#xff0c;帮助你全面掌握 IDE&#xff0c;提高编程体验…...

【MySQL】使用文件进行交互

目录 准备工作 1.从文本文件中读取数据&#xff08;导入&#xff09; 1.1.CSV 文件 1.2.设置导入导出的路径 1.3.导入文件 1.4.将数据写入文本文件&#xff08;导出&#xff09; 2.从文件中读取并执行SQL命令 2.1.通过mysql监视器执行编写在文件里面的SQL语句 2.2.通过…...

Redis 学习笔记 5:分布式锁

Redis 学习笔记 5&#xff1a;分布式锁 在前文中学习了如何基于 Redis 创建一个简单的分布式锁。虽然在大多数情况下这个锁已经可以满足需要&#xff0c;但其依然存在以下缺陷&#xff1a; 事实上一般而言&#xff0c;我们可以直接使用 Redisson 提供的分布式锁而非自己创建。…...

【硬核数学】2. AI如何“学习”?微积分揭秘模型优化的奥秘《从零构建机器学习、深度学习到LLM的数学认知》

在上一篇中&#xff0c;我们探索了线性代数如何帮助AI表示数据&#xff08;向量、矩阵&#xff09;和变换数据&#xff08;矩阵乘法&#xff09;。但AI的魅力远不止于此&#xff0c;它最核心的能力是“学习”——从数据中自动调整自身&#xff0c;以做出越来越准确的预测或决策…...

[Java][Leetcode middle] 151. 反转字符串中的单词

思路挺简单的 自己想的&#xff0c;步骤挺复杂的 先统计处开头和结尾的空格数跳过开头这些空格&#xff0c;将单词放到数组中统计最后一个可能漏过的单词&#xff08;例如&#xff1a;“hello word”&#xff0c;没有空格退出&#xff09;倒序输出 public String reverseWor…...

力扣每日一题5-18

class Solution { public int colorTheGrid(int m, int n) { // 每一列可能的状态总数 每个单元有3可能 int totalState 1; for (int i 0; i < m; i) totalState * 3; // pre[k] 代表前一轮dp 状态为k 的方案总数 int [] pre new int [totalState]; // 初始化合法填色 的…...

leetcode 74. Search a 2D Matrix

题目描述 要求时间复杂度必须是log(m*n)。那么对每一行分别执行二分查找就不符合要求&#xff0c;这种做法的时间复杂度是m*log(n)。 方法一&#xff0c;对每一行分别执行二分查找&#xff1a; class Solution { public:bool searchMatrix(vector<vector<int>>&a…...

养生指南:重塑健康生活的实用方案

一、饮食&#xff1a;均衡膳食&#xff0c;滋养身心 三餐以 “轻盐、轻油、轻糖” 为准则。早餐搭配全麦三明治、无糖酸奶和一小把蓝莓&#xff0c;补充优质碳水与抗氧化物质&#xff1b;午餐选用糙米饭、白灼虾及蒜蓉西蓝花&#xff0c;保证蛋白质与膳食纤维摄入&#xff1b;…...

IPTABLES四表五链祥解

在Linux中&#xff0c;iptables 是一个强大的防火墙工具&#xff0c;用于管理和过滤网络流量。iptables 使用四个不同的表&#xff0c;每个表都包含多个链&#xff0c;来控制流量的处理。 一、iptables四个表 表名功能说明filter默认表&#xff0c;负责对进出数据包的过滤操作…...

嵌入式学习--江协51单片机day8

这个本来应该周末写的&#xff0c;可是一直想偷懒&#xff0c;只能是拖到周一了&#xff0c;今天把51结个尾&#xff0c;明天开始学32了。 学习内容LCD1602&#xff0c;直流电机&#xff0c;AD/DA&#xff0c;红外遥控 LCD1602 内部的框架结构 屏幕小于数据显示区&#xff…...

内网穿透与内网映射是什么?

在互联网技术快速迭代的当下&#xff0c;网络通信架构日益复杂&#xff0c;内网穿透与内网映射作为实现公网访问内网资源的核心技术&#xff0c;在企业办公、个人开发、智能家居等领域发挥着关键作用。尽管两者都致力于打通公网与内网的连接通道&#xff0c;但它们在底层原理、…...

51单片机点亮一个LED介绍

LED介绍 LED就是发光二极管&#xff0c;一般来说如果是直插式的&#xff0c;那就是长正短负&#xff0c;如果是贴片式的&#xff0c;那就带彩色标记是阴极&#xff0c;如果是三角形的&#xff0c;水平箭头指的就是阴极&#xff0c;通常一般的工作电压在3mA~20mA&#xff0c;当…...

WebRTC技术EasyRTC嵌入式音视频通信SDK助力智能电视搭建沉浸式实时音视频交互

一、方案概述​ EasyRTC是一款基于WebRTC技术的开源实时音视频通信解决方案&#xff0c;具备低延迟、高画质、跨平台等优势。将EasyRTC功能应用于智能电视&#xff0c;能够为用户带来全新的交互体验&#xff0c;满足智能电视在家庭娱乐、远程教育、远程办公、远程医疗等多种场…...

uniapp 小程序 CSS 实现多行文本展开收起 组件

效果 组件 <template><!-- 最外层弹性盒子 --><div class"box" :style"boxStyle"><!-- 文本区域&#xff0c;动态类名控制展开/收起状态 --><div ref"textRef" :class"[text-cont, btnFlag ? text-unfold : t…...

嵌入式51单片机:C51

sbit TISCON^1的意思是定义TI为SCON的次低位&#xff08;最低位标记为0&#xff0c;其次为1&#xff0c;再次为2&#xff09;...

【回眸】香橙派zero2 嵌入式数据库SQLite

前言 SQLite介绍 安装SQLite3 SQLite 使用 创建数据库 创建一张表格 插入数据 查看数据库的记录 删除一条记录 更改一条记录 删除一张表 增加一列&#xff08;性别&#xff09; SQLite编程操作 前言 还有2个项目没更新完...披星戴月更新中... SQLite介绍 基于嵌入…...

vue3个生命周期解析,及setup

合理使用各生命周期&#xff0c;切勿乱用&#xff0c;不是所有东西都需要&#xff0c;合理使用可以提高效率和性能。 Vue 3 生命周期钩子详解 Vue 3的生命周期钩子分为以下几个阶段&#xff1a; onBeforeMount 调用时机&#xff1a;在组件挂载到DOM之前调用。使用场景&#xf…...