news 2026/9/5 12:36:29

UE5实时录屏:基于FFmpeg的RenderTarget采集与编码方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UE5实时录屏:基于FFmpeg的RenderTarget采集与编码方案

简介:UE5实时录屏插件(FFmpeg)是一套基于FFmpeg库实现的UE5实时屏幕录制解决方案,面向需要在Windows/Linux平台为项目增加录制能力的游戏开发者,解决UE5没有内置录屏功能的问题。资源包为zip压缩包,共570个文件,约164.02MB,包含346个h头文件、29个dll动态库、26个lib导入库、多个c源文件及14个pc配置文件与14个html文档,其中头文件与库文件对应不同编译环境,文档提供配置参考,涵盖FFmpeg编译产物、接口封装与使用说明等。已有3005人学习下载,得到开发者广泛关注。资源内置EasyFFMPEG-main封装源码、使用说明.docx以及一整套FFmpeg开发库,能够帮助读者从库的引入、接口封装到逐帧编码调用完整落地,理解avformat/avcodec等API的调用方式,并结合SceneCaptureComponent2D实现逐帧数据转码、线程同步与性能优化等关键环节。适合希望深入掌握UE5与FFmpeg集成技巧、具备一定C++基础的中高级开发者作为参考模板。

1. 为什么UE5实时录屏要选FFmpeg

1.1 引擎自带录屏方案的真实痛点

做UE5项目时,录屏需求从来不是"能录就行"这么简单。我最早偷懒直接用引擎自带的Movie Render Queue,结果踩了一脚泥:它走的是离线渲染管线,录制一帧可能要几十毫秒甚至更久,完全不适合"边跑边录"的场景。你要录一个运营Demo、录一个自动化测试结果、或者在游戏里加一个"一键分享录像"功能,Movie Render Queue的延迟渲染会直接拖垮帧率,而且中途切换关卡、玩家进出场都会让录像端掉链子。

另一个常见思路是用Windows系统级录屏,比如Game Bar、Windows Graphics Capture(WGC)。Game Bar确实零成本,但它不受引擎控制,开始、停止、文件命名全部依赖用户手动操作,没法嵌入到项目流程里;WGC API能定制,但它是COM组件风格,工程结构繁琐,而且抓取的是桌面/窗口画面,不是UE5内部的RT视口,多显示器、DWM合成、分辨率缩放等一堆边缘Case能把你磨到怀疑人生。

这就说到了FFmpeg的价值:它是跨平台的多媒体处理库,封装了视频编码、复用、推流、滤镜在内的全套能力。把FFmpeg嵌进UE5,直接截取引擎渲染出来的RT(Render Target)像素,在内存里交给libx264编码成H.264,再封装成MP4。整个过程完全在引擎内闭环,渲染线程、游戏线程、编码线程互不干扰,录制开始/停止时机、分辨率、码率、帧率全部由你决定。这正是做工具链、自动化测试、游戏内录像功能最需要的自由度。

1.2 什么样的人适合这个方案

我做了这几年UE5工具链开发,见过太多人在录屏这里临时抱佛脚:要么用外部软件录,要么用引擎离线渲染录,最后都无法和自动化流程打通。如果你属于下面任何一类,FFmpeg方案值得认真投入:

  • 做UE5编辑器工具链,需要批量录屏做自动化截图/录像回归测试的。
  • 做数字孪生、看板、展览类项目,需要长时间循环播放并实时记录画面的。
  • 做带"录像回放"功能的游戏或仿真应用,想把这套能力集成进产品的。
  • 做像素流或者其他音视频服务,本身就需要和FFmpeg打交道的。

简而言之,只要"录制"不是一次性手工操作,而是产品的一个功能模块,就必须在引擎内实现本地编码。这也是我想把这篇文章写完整的原因。

2. 插件整体设计与方案选型

2.1 不同录屏路线的对比

不光是UE5本身有自带方案,FFmpeg其实也有好几种接入路径。很多人一上来就纠结"用FFmpeg的gdigrab不就行了吗",如果你真试过就会知道,gdigrab抓的是屏幕,不是UE5的视口。某个项目里用户开了高分屏缩放,录出来的分辨率乱七八糟,字还发虚,根本没法用。核心区别在于:

方案采集源实时性可定制性工程集成复杂度适合场景
Movie Render Queue引擎离线渲染缓冲影视级离线输出
Windows Graphics Capture桌面/窗口系统级窗口录制
FFmpeg gdigrab桌面临时录屏
FFmpeg + UE RenderTargetUE视口/RT引擎内嵌录屏、录像回放

我的结论很明确:真正要在UE5产品里做实时录屏,必须走"引擎RT采集 + FFmpeg编码"的路线。这样录什么、录多清晰、录多大码率全在引擎里控制,还能配合关卡流送、Camera切换,做到游戏逻辑层面绝对同步。

2.2 插件流水线架构

在设计插件之前,先想想数据从渲染完成到文件落盘经历了什么。按照从业者的思维习惯,我习惯把它拆成四段:

  1. 采集段:从UE5渲染管线拿到一帧BGRA8或者RGBA8像素数据。
  2. 转换段:H.264编码器要求YUV420空间(实际是YUV4:2:0布局),需要把BGRA转成YUV,同时做RGB->YUV色彩空间变换和降采样。
  3. 编码段:libx264拿到YUV帧,按预设的码率、GOP、preset进行编码,输出H.264 Annex-B流。
  4. 封装段:把编码后的包写入MP4/FLV/MKV容器,生成时间戳、音视频索引等元数据。

这四段中任何一段慢了,整个录像帧率就会掉。我在项目里遇到最典型的坑是把像素转换放在游戏线程做,结果直接掉帧20多。原因很简单:ReadPixels从GPU读回CPU本身就带一次同步卡顿,再叠加sws_scale做颜色转换,游戏线程根本扛不住。所以我后来把所有CPU密集操作全部挪到独立编码线程,游戏线程只做两件事:发一帧数据到队列,然后立即返回。

2.3 FFmpeg库的集成方式

FFmpeg集成到UE5时,很多人纠结静态库还是动态库。我的实践经验是:

  • 开发阶段用动态库(DLL)。升级版本、排查问题都方便,不用每次重新链接整个工程。
  • 正式分发阶段如果需要单包部署,再考虑静态链接到exe里,避免DLL缺失被用户投诉。

不管哪种方式,FFmpeg的头文件和库建议统一放到插件目录下的ThirdParty/FFmpeg,方便后面打包。需要引入的头文件大部分集中在libavformatlibavcodeclibavutillibswscale几个模块。如果编码器选择libx264,那还需要确认FFmpeg构建时开启了这个模块,很多纯Lite版没有x264,录出来的东西格式兼容性会差很多。

3. 核心模块实现细节

3.1 像素采集:RenderTarget还是BackBuffer

这一块是很多自己写过录屏的人最容易翻车的地方。UE5里拿到渲染像素的数据源主要有三条路:

  1. SceneViewport::OnBackBufferReadyToPresent回调:发生在后备缓冲区即将被Present的时刻,可以直接拿到FTexture2DRHIRef。数据非常新鲜,但需要注意这个回调在RHI线程上触发,且纹理生命周期很短,不能在回调里直接做长时间操作。正确做法是立即发起异步拷贝,把像素拷到StagingTexture,再在另一个线程Map后读出CPU内存。

  2. SceneCapture2D+RenderTarget:可以指定相机和分辨率,把渲染内容渲染到RT上,然后通过FRenderTarget::ReadPixels读回CPU。好处是灵活,可以录任意视角、任意分辨率;坏处是多了一次场景渲染或场景拷贝开销。如果你的需求是"录当前主视口",这个方案会多出一份冗余开销。

  3. 自定义BeginRenderCommand在RHI线程做CopyTexture到StagingBuffer。这是效率最高的路,也是我在正式项目里用的方式。它绕过了ReadPixels的同步等待,直接用GPU拷贝,拷贝完成后异步映射回CPU,编码线程拿数据时完全不用卡渲染。

我给插件的默认实现选了第3种,但在配置项里保留"兼容模式"让用户切换到SceneCapture2D方案。为什么?因为StagingBuffer这套对多平台、多RHI(DX11/DX12/Vulkan)有一些细节差异,新手排查起来太崩溃。兼容模式虽然慢一点,但能保证先跑通流程。

实际代码里核心就这几行:

// 在OnBackBufferReadyToPresent回调中发起异步拷贝 FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList(); void* RHITexture = BackBuffer->GetNativeResource(); RHICmdList.Transition(FRHITransitionInfo(BackBuffer, ERHIAccess::Present, ERHIAccess::CopySrc)); RHICmdList.CopyTexture( FRHICopyTextureInfo{}, BackBuffer, StagingTexture );

然后编码线程对StagingTexture调用RHICmdList.MapStagingSurface取出像素缓冲。如果你只是想快速验证,直接ReadPixels也够用,但要知道它会把渲染线程和游戏线程一起拖慢。

3.2 FFmpeg编码流程的关键步骤

FFmpeg是一套纯C接口的库,调用顺序错了轻则崩溃重则文件打不开。我建议按照固定的生命周期模板来写,别跳步:

// 1. 分配输出上下文 AVFormatContext* FormatCtx = nullptr; avformat_alloc_output_context2(&FormatCtx, nullptr, nullptr, OutputFilename); // 2. 创建视频流并绑定编码器 AVStream* Stream = avformat_new_stream(FormatCtx, nullptr); AVCodec* Codec = avcodec_find_encoder_by_name("libx264"); AVCodecContext* CodecCtx = avcodec_alloc_context3(Codec); // 3. 设置编码参数 CodecCtx->width = Width; CodecCtx->height = Height; CodecCtx->time_base = {1, static_cast<int>(FPS)}; CodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; CodecCtx->codec_type = AVMEDIA_TYPE_VIDEO; CodecCtx->bit_rate = Bitrate; // 例如 8'000'000 表示8Mbps CodecCtx->gop_size = FPS * 2; // 关键帧间隔2秒 // 4. 打开编码器 if (avcodec_open2(CodecCtx, Codec, nullptr) < 0) { /* 处理错误 */ } // 5. 打开输出文件并写入头部 avio_open(&FormatCtx->pb, OutputFilename, AVIO_FLAG_WRITE); avformat_write_header(FormatCtx, nullptr); // 6. 每次编码一帧后调用 avcodec_send_frame(CodecCtx, Frame); AVPacket Pkt; av_new_packet(&Pkt, Size); avcodec_receive_packet(CodecCtx, &Pkt); av_interleaved_write_frame(FormatCtx, &Pkt); // 7. 结束录制 avcodec_send_frame(CodecCtx, nullptr); while (avcodec_receive_packet(CodecCtx, &Pkt) == 0) { av_interleaved_write_frame(FormatCtx, &Pkt); } av_write_trailer(FormatCtx); avio_closep(&FormatCtx->pb); avformat_free_context(FormatCtx);

这里面最容易忽略的是第6步只处理了单帧,没有处理编码器内部缓存的多帧数据。你录完视频发现最后1-2秒缺失或者时长比实际短,基本都是因为在结束时没有把编码器缓冲的帧刷出来。avcodec_send_frame(CodecCtx, nullptr)就是刷缓冲的关键动作,一定不能漏。

另一个容易错的是PTS。网上很多文章说PTS用帧序号i就行,这在固定帧率下勉强能用,但一旦丢帧或者帧率波动,录像就会出现"快进/卡住"的现象。正确做法是用递减的PtsCounter,然后写入时根据实际采集时间计算:

Frame->pts = PtsCounter++;

配合CodecCtx->time_base和封装容器的time_base,FFmpeg会把播放速率控制在正确节奏上。

3.3 多线程同步与帧率控制

录屏插件一旦跑起来,就是一条三线程流水线:游戏线程产生帧、编码线程做转换和编码、IO线程写文件。最常见的错误是给编码线程挂一个无界队列,结果录制5分钟后内存涨了2个G,玩家开始骂娘。

解决方法是设计一个"带丢弃策略的环形缓冲":队列固定3-5帧,如果编码线程跟不上,新帧直接覆盖队首最旧的那一帧。对录屏来说,丢几帧远好过内存爆炸。你可以把采集帧率独立于游戏帧率,比如游戏跑120帧,录像只需要30帧,那游戏线程每4帧才抓取1次,大幅减轻编码负担。

帧率控制我不建议直接凭空造轮子。用真实时间来驱动是更稳的:

double Accumulator = 0.0; double LastTime = FPlatformTime::Seconds(); double FrameInterval = 1.0 / TargetFPS; // 每次从队列获取渲染帧时 double Now = FPlatformTime::Seconds(); Accumulator += Now - LastTime; LastTime = Now; if (Accumulator >= FrameInterval) { Accumulator = FMath::Fmod(Accumulator, FrameInterval); // 执行编码帧写入 }

这样即使某帧因为加载地图、打开UI之类卡顿,录像时间轴也不会凭空多出内容。

3.4 音频要不要做

这个问题一开始就要想清楚。我的经验是第一版建议只做视频轨,先把画面链路跑通,后面再加音频。原因很现实:音频采集在UE5里要分两条路,一条是抓WASAPI系统环回设备(录系统的声音,但用户可能没开环回),另一条是从UE的Audio Engine拿PCM数据(用FAudioDevice的混音回调),两条路都要同步管理音视频时间戳,复杂度翻倍。

如果确实需要音频,我记得后来项目里是用WASAPI的AUDCLNT_STREAMFLAGS_LOOPBACK抓环回,把PCM喂给FFmpeg的AAC编码器,并单独开一条音频线程维持PTS。但是同步问题至今都很烦人:常出现视频先行、音频迟到半秒的怪现象。所以如果实在要音频,建议直接参考FFmpeg官方muxing.c示例里的音视频双流写法,不要自己凭感觉造。

4. 从零搭建插件并跑通的实操记录

4.1 准备FFmpeg库文件

Windows环境下我比较推荐用预编译的release包,节省宝贵时间。下载时要注意架构必须选x64,版本选较新的stable(比如4.4/5.1/6.1都行)。解压后的目录结构整理成:

ThirdParty/FFmpeg/ ├─ include/ │ ├─ libavcodec/ │ ├─ libavformat/ │ ├─ libavutil/ │ └─ libswscale/ ├─ lib/ │ ├─ avcodec.lib │ ├─ avformat.lib │ ├─ avutil.lib │ └─ swscale.lib └─ bin/ ├─ avcodec-*.dll ├─ avformat-*.dll ├─ avutil-*.dll └─ swscale-*.dll

注意:发行DLL时,FFmpeg 6.0以后的DLL文件名带-61这种版本后缀,打包时要一起拷贝,别只拷一个不拷另一个。不然用户本机缺少运行库,录制功能直接失效。

4.2 在UE5里创建插件模块

用编辑器自带的New Plugin工具创建一个Blank插件,然后在Build.cs里配置第三方库:

public class FFmpegRecorder : ModuleRules { public FFmpegRecorder(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "../ThirdParty/FFmpeg/include")); PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "../ThirdParty/FFmpeg/lib/avcodec.lib")); PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "../ThirdParty/FFmpeg/lib/avformat.lib")); PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "../ThirdParty/FFmpeg/lib/avutil.lib")); PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "../ThirdParty/FFmpeg/lib/swscale.lib")); // 开发阶段使用delay load,让DLL在运行时加载 PublicDelayLoadDLLs.AddRange(new string[] { "avcodec-61.dll", "avformat-61.dll", "avutil-61.dll", "swscale-7.dll" }); RuntimeDependencies.Add("$(BinaryOutputDir)/avcodec-61.dll"); RuntimeDependencies.Add("$(BinaryOutputDir)/avformat-61.dll"); RuntimeDependencies.Add("$(BinaryOutputDir)/avutil-61.dll"); RuntimeDependencies.Add("$(BinaryOutputDir)/swscale-7.dll"); } }

RuntimeDependencies是打包时必写的,不然打包后的程序跑起来提示找不到DLL。我早期忽略过这个,编辑器里能录,打包后立刻报错。

4.3 核心封装类

我会把FFmpeg相关逻辑封装成一个原生C++类FFFmpegVideoEncoder,不直接塞进Actor,方便随时在工具、Actor、插件后台中使用。核心接口大致这样:

class FFFmpegVideoEncoder { public: bool StartRecording(const FString& FilePath, int32 Width, int32 Height, int32 FPS, int32 Bitrate); bool WriteFrame(const uint8* BGRA8Data, int32 Width, int32 Height); void StopRecording(); private: AVFormatContext* FormatCtx = nullptr; AVCodecContext* CodecCtx = nullptr; AVFrame* Frame = nullptr; SwsContext* ScaleCtx = nullptr; };

WriteFrame内部做两件事:用sws_scale把BGRA8的AVFrame转成YUV420,然后avcodec_send_frame。注意sws_scale的输入参数要对应UE的BGRA存储方式,一般指定AV_PIX_FMT_BGRA。这里有一个非常经典的坑:UE5的FColor在内存里是BGRA顺序,你如果按RGBA配置sws_scale,录出来的画面会红蓝互换——我第一次遇到时排查了大半天。

4.4 给蓝图暴露接口

插件最终给谁用?大概率是蓝图开发者或设计师,你不可能要求他们去写C++。所以我的习惯是把插件核心逻辑封装在UFunction(BlueprintCallable)里,在UActorComponent上暴露三个接口:

  • StartRecording:传入文件名、分辨率、FPS、码率。
  • StopRecording:停止编码并关文件。
  • IsRecording:查询状态。

蓝图侧只需在关卡Bluprint里拖个UI按钮,点击时调用StartRecording,再次点击时调用StopRecording。像素采集部分的逻辑完全藏在组件内部,不暴露给蓝图。这样维护起来边界清晰,谁都不越界。

5. 常见问题与排查技巧实录

5.1 问题速查表

现象直接原因解决方式
录出来的视频绿屏/花屏像素格式配置错误(BGRA/RGBA/YUV不对)检查sws_scale的输入格式,UE用AV_PIX_FMT_BGRA
视频里红色和蓝色互换颜色通道顺序未对齐改用AV_PIX_FMT_BGRA或者手动交换R/B
录到一半内存持续增长帧队列无界堆积改用固定大小环形缓冲,并加丢帧策略
录制结束文件打不开没有调用av_write_trailer正确flush编码器缓冲并写尾
打包后提示DLL缺失没有配置RuntimeDependencies或不带DLL在Build.cs里配置RuntimeDependencies
找不到libx264编码器FFmpeg版本未编译x264换带x264的full版,或改用libsvtav1等其他编码器
视频前几秒卡顿没有设置GOP大小或者B帧策略异常显式设置gop_size和max_b_frames=0
录制中游戏掉帧明显像素读取导致CPU/GPU同步阻塞改用CopyTexture+StagingBuffer异步映射
视频时长和实际录制时间不符PTS计算错误用真实的采集时间戳计算帧PTS
音视频不同步音视频PTS基准不一致用同一条时间轴,不要用各自的帧计数

5.2 我最想单独拎出来讲的两个坑

第一个是编码器缓存机制。很多人录到结尾发现视频突然缺最后一两秒,这是老生常谈但依然频繁发生。本质上是H.264编码器为了压缩率会缓存几帧在未来帧之后输出,如果你录制结束时没有把缓存中的帧冲洗出来,它们就永远不写进文件。解决方式就是上面提到的:avcodec_send_frame(CodecCtx, nullptr),然后循环avcodec_receive_packet直到返回AVERROR_EOF,再把包全部写进文件。

第二个是长时间录制的崩溃。项目里遇到过录制4小时后内存涨到14G的情况,最终定位是每一帧在av_packet_unref上少调用了一次。AVPacket是个引用计数容器,av_interleaved_write_frame不会自动释放你传入的packet内存,每次用完后必须av_packet_unref(&Pkt),否则就是妥妥的内存泄漏。类似的还有av_frame_unref。写编码循环时必须保证每个AVPacket、AVFrame都用一次unref一次,顺序错一个就有隐患。

5.3 性能调优心得

录屏功能上线前,我会先固定一个参数组合来跑基准测试:1080p、30fps、8Mbps、preset=veryfast。这几个参数对大部分游戏足够清晰,体积也不会失控。如果录制过程平均掉帧小于3帧,就会尝试把preset降为medium;如果掉帧明显,先检查是不是编码线程CPU占用已经到了95%以上,是就把preset调回fast或faster,而不是优先去砍码率。码率过低反而会导致画面模糊,视觉体验更差。

还有一个很少有人提的点:FFmpeg的av_interleaved_write_frame可能会在IO速度慢时阻塞,这意味着编码线程长时间卡在文件写入上。稳妥的做法是编码线程和文件IO线程分离,编码线程写完包后挂到IO队列,IO线程统一写磁盘。但一般项目直接在编码线程里写磁盘也够用,除非录的是4K超高清,才需要考虑拆IO线程。

6. 最后再分享一点实际经验

这套插件方案我在三个项目里落地过,从最初的ReadPixels硬抗,到后来改成StagingBuffer异步拷贝,再到现在跑得顺滑稳定的版本,中间踩过的坑不少。最深的体会是:FFmpeg本身并不复杂,复杂的是和UE5渲染管线的集成时机、线程模型和资源生命周期管理。只要这些理顺,后面加音频轨、加推流直播、加分段录制都只是顺着Pipeline再扩展模块而已。

另外提醒一句,FFmpeg的许可证在某些使用场景下是GPL/LGPL的。如果你只是内部工具用,问题不大;如果发出去给商业用户,提前走一遍授权检查,别等到产品都上架了才想起这回事,到时候换编码器重写一遍成本极高。技术选型做在前面,永远比事后补窟窿省心。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 1:17:04

SkeyeVSS 3.2.0实战:国标接入与流媒体分发全解析

简介&#xff1a;这是一款面向视频监控与GB28181平台开发测试人员的公共测试软件。SkeyeVSS-3.2.0以中心信令管理服务为核心&#xff0c;支持设备注册、心跳检测、视频流调度、事件通知与会话控制等关键功能&#xff0c;适配Windows系统部署&#xff0c;可帮助技术人员在本地快…

作者头像 李华
网站建设 2026/9/6 1:18:16

COM+编程实战:从COM到企业级组件服务

简介&#xff1a;COM编程资料包是一套面向企业级开发者的组件服务学习资源&#xff0c;聚焦COM在事务、安全、事件、并发及分布式场景下的实际应用&#xff0c;适合已掌握COM基础并希望进阶的读者。包体共1339个文件&#xff0c;以cpp、h源代码文件为核心&#xff0c;辅以idl接…

作者头像 李华
网站建设 2026/9/6 1:21:16

从《后西游记》看AIGC长剧生产链路与边审边播工程化

国内第一部 AIGC 长剧《后西游记》今天正式开播了&#xff0c;而且打出的标签是首部“边审边播”的剧集。这两个信息叠在一起&#xff0c;比“又一部 AI 短剧上线”要重得多。短剧和长剧的生产流程完全不同&#xff1a;短剧 3 分钟一个单元&#xff0c;AI 生成还能靠人工盯过去…

作者头像 李华
网站建设 2026/9/5 3:49:46

滴滴Robotaxi R2无人载客测试:自动驾驶落地的关键在数据闭环

自动驾驶行业的观察点&#xff0c;最近已经从“这辆车上装了几颗雷达”变成了“普通用户到底能不能打上车”。滴滴自动驾驶新一代Robotaxi R2在北京、广州开启无人载客测试&#xff0c;意味着乘客可以在指定运营区域内&#xff0c;通过平台叫到一台没有安全员的自动驾驶车辆。很…

作者头像 李华
网站建设 2026/9/5 11:40:40

海尔75H5D电视深度评测:75英寸4K 165Hz高刷是否真香?

1. 先搞清楚“性价比之王”到底值不值得看如果你正在看75英寸4K电视&#xff0c;预算卡在4000-5000元这个档位&#xff0c;并且对高刷新率有需求&#xff0c;那海尔75H5D这款电视确实值得你花几分钟仔细研究一下。它最核心的卖点很直接&#xff1a;75英寸、4K分辨率、165Hz高刷…

作者头像 李华
网站建设 2026/9/4 21:59:00

STM32F10x官方例程详解:从GPIO到DMA的工程实践指南

简介&#xff1a;STM32F10x官方例程是一套由意法半导体提供的嵌入式开发示例集合&#xff0c;面向使用ARM Cortex-M3内核的STM32F10x系列微控制器的开发者与学习者。资源系统覆盖GPIO、定时器、ADC/DAC、USART/SPI、I2C、USB、CAN、DMA、RTC、EXTI及FFT等常见外设模块&#xff…

作者头像 李华