FFmpeg API 实战:读取视频信息与避坑指南
FFmpeg 这个库做音视频开发的没人绕得开。但很多人第一次翻开它的头文件看到AVFormatContext、AVCodecParameters、AVStream这一堆结构体第一反应是懵的——文档少、示例老、API 还老变。我当初也是这样为了拿一个视频的宽高和时长硬是折腾了大半天。这篇就聊聊怎么用 FFmpeg 的 API 把视频信息读出来包括容器层面的元数据、每条流的编码参数以及那些文档里不写、但实际会踩的坑。适合刚接触 FFmpeg、准备做播放器、转码工具或者媒体管理系统的朋友也适合已经用过命令行但想转到 API 层面的人。1. 先搞清楚 FFmpeg 到底能读出哪些信息很多人以为获取视频信息就是拿个时长和分辨率其实远不止。FFmpeg 把信息分成两个层次容器层和流层。容器层描述的是这个文件本身比如格式名称、时长、比特率、元数据标签标题、作者、创建时间这些流层描述的是文件里每一条独立的轨道比如视频流、音频流、字幕流每条流有自己的编码格式、分辨率、采样率、帧率等。1.1 容器层信息文件的身份证容器层对应的是AVFormatContext这个结构体。当你调用avformat_open_input打开一个文件后这个结构体里就填满了容器相关的信息。几个关键字段iformat输入格式比如 mov,mp4,m4a,3gp,3g2,mj2 这种它告诉你 FFmpeg 是用哪个 demuxer 解析的这个文件。duration时长单位是 AV_TIME_BASE也就是微秒。注意这个值不一定准尤其是某些流式格式或者文件头没写好的情况。bit_rate整体比特率单位是 bit/s。nb_streams流的数量。metadata一个AVDictionary里面是键值对形式的元数据。这里有个容易忽略的点duration在打开文件后可能还是 0 或者一个很小的值因为有些格式的时长信息在文件末尾。这时候你需要调用avformat_find_stream_info之后再看或者干脆自己根据帧数和帧率估算。1.2 流层信息每条轨道的体检报告每条流对应一个AVStream通过formatContext-streams[i]访问。AVStream里最重要的两个东西是codecpar编码参数和time_base时间基。codecpar是AVCodecParameters类型它替代了老版本里的AVCodecContext在打开解码器之前的那部分信息。里面包含codec_type流类型AVMEDIA_TYPE_VIDEO、AVMEDIA_TYPE_AUDIO、AVMEDIA_TYPE_SUBTITLE等。codec_id编码器 ID比如AV_CODEC_ID_H264、AV_CODEC_ID_AAC。视频相关width、height、format像素格式、bit_rate。音频相关sample_rate、channels、channel_layout、frame_size。time_base是个AVRational分子分母的形式。视频流通常是 1/90000 或者 1/1000 这种音频流可能是 1/44100。这个值决定了流里时间戳的单位后面做时间换算全靠它。提示codecpar里的bit_rate经常是 0尤其是视频流。别指望它真要算比特率得自己根据文件大小和时长估。2. 打开文件与读取流信息的完整流程光看结构体没用得知道按什么顺序调用 API。FFmpeg 的 API 设计是先打开再探测最后读顺序错了要么拿不到数据要么直接崩。2.1 第一步avformat_open_input 打开输入AVFormatContext *fmt_ctx NULL; int ret avformat_open_input(fmt_ctx, filename, NULL, NULL); if (ret 0) { // 打开失败用 av_strerror 拿错误信息 char errbuf[128]; av_strerror(ret, errbuf, sizeof(errbuf)); fprintf(stderr, Could not open file: %s\n, errbuf); return ret; }这里第一个参数是二级指针传NULL进去FFmpeg 会自己分配一个AVFormatContext。第三个参数是强制指定输入格式一般传NULL让它自动探测。第四个参数是AVInputFormat的选项字典也传NULL。打开成功后fmt_ctx-iformat、fmt_ctx-nb_streams这些就已经有值了。但注意这时候流里的编码参数可能还不完整尤其是网络流或者某些特殊容器。2.2 第二步avformat_find_stream_info 补全信息ret avformat_find_stream_info(fmt_ctx, NULL); if (ret 0) { fprintf(stderr, Failed to find stream info\n); avformat_close_input(fmt_ctx); return ret; }这个函数会实际去读一部分数据通常是几帧来探测每条流的真实编码参数。对于本地文件它很快对于网络流可能会阻塞一会儿。它的作用是把codecpar里的width、height、sample_rate这些填完整。我见过有人跳过这一步直接读codecpar-width结果拿到 0然后到处找 bug。这一步不能省除非你非常确定文件头里已经写全了所有信息。2.3 第三步遍历流提取需要的信息for (unsigned int i 0; i fmt_ctx-nb_streams; i) { AVStream *stream fmt_ctx-streams[i]; AVCodecParameters *par stream-codecpar; if (par-codec_type AVMEDIA_TYPE_VIDEO) { printf(Video stream #%u\n, i); printf( Codec: %s\n, avcodec_get_name(par-codec_id)); printf( Resolution: %dx%d\n, par-width, par-height); printf( Pixel format: %s\n, av_get_pix_fmt_name(par-format)); printf( Time base: %d/%d\n, stream-time_base.num, stream-time_base.den); } else if (par-codec_type AVMEDIA_TYPE_AUDIO) { printf(Audio stream #%u\n, i); printf( Codec: %s\n, avcodec_get_name(par-codec_id)); printf( Sample rate: %d\n, par-sample_rate); printf( Channels: %d\n, par-channels); } }avcodec_get_name能把AV_CODEC_ID_H264转成 h264 这种可读字符串。av_get_pix_fmt_name同理把AV_PIX_FMT_YUV420P转成 yuv420p。2.4 第四步别忘了 avformat_close_inputavformat_close_input(fmt_ctx);这个函数会释放AVFormatContext及其内部所有流、元数据占用的内存并把指针置为NULL。不调用就是内存泄漏。我见过一个项目跑久了内存一直涨最后查出来就是打开文件后没关。3. 时长、帧率、比特率三个最容易算错的值这三个值看起来简单但 FFmpeg 给出来的原始数据往往不能直接用得换算。3.1 时长微秒、时间基、还是秒fmt_ctx-duration的单位是微秒AV_TIME_BASE 1000000。所以double duration_sec fmt_ctx-duration / (double)AV_TIME_BASE;但前面说了这个值可能是 0。更可靠的做法是用流的时长AVStream *video_stream ...; double stream_duration video_stream-duration * av_q2d(video_stream-time_base);av_q2d把AVRational转成double。stream-duration的单位是stream-time_base乘上时间基的倒数就是秒。如果两个都是 0 或者AV_NOPTS_VALUE那就只能靠读帧来数了。这种情况在直播流或者某些损坏文件里很常见。3.2 帧率r_frame_rate 和 avg_frame_rate 的区别AVStream里有两个帧率字段r_frame_rate基础帧率通常是实际帧率的近似值比如 30000/1001。avg_frame_rate平均帧率根据实际帧的时间戳算出来的。对于恒定帧率CFR的视频两者基本一致。对于可变帧率VFR的视频avg_frame_rate更接近真实观感但r_frame_rate才是解码器实际用的。AVRational fr stream-avg_frame_rate; if (fr.num ! 0 fr.den ! 0) { double fps av_q2d(fr); printf(FPS: %.2f\n, fps); }注意分母为 0 的情况直接除会崩。必须先判断。3.3 比特率文件大小除以时长最靠谱fmt_ctx-bit_rate有时候是 0codecpar-bit_rate也经常是 0。最稳的办法是自己算// 假设你已经知道文件大小 file_size字节 double bitrate (file_size * 8) / duration_sec;单位是 bit/s。如果要更精确可以分别算视频流和音频流的比特率但那就得解析 packet 了属于进阶操作。4. 元数据读取AVDictionary 的用法与坑元数据就是那些标题作者专辑之类的标签。FFmpeg 用AVDictionary存键值都是字符串。4.1 遍历元数据AVDictionaryEntry *tag NULL; while ((tag av_dict_get(fmt_ctx-metadata, , tag, AV_DICT_IGNORE_SUFFIX))) { printf(%s %s\n, tag-key, tag-value); }av_dict_get的第二个参数是键名传空字符串表示匹配所有键。第三个参数是上一次返回的 entry用来迭代。第四个参数AV_DICT_IGNORE_SUFFIX表示忽略键的后缀匹配。4.2 读取特定键AVDictionaryEntry *title av_dict_get(fmt_ctx-metadata, title, NULL, 0); if (title) { printf(Title: %s\n, title-value); }注意键名是大小写敏感的而且不同容器用的键名不一样。MP4 里可能是 titleMKV 里可能是 TITLE。实际项目里通常得准备一个映射表。4.3 流级别的元数据每条流也有自己的metadata比如语言标签AVDictionaryEntry *lang av_dict_get(stream-metadata, language, NULL, 0);这个在有多音轨的视频里很有用能告诉用户哪条是中文、哪条是英文。注意元数据里的字符串编码不一定是 UTF-8有些老文件是 GBK 或者 Latin-1。直接 printf 可能乱码。稳妥的做法是先检测编码再转换或者至少做个容错。5. 那些文档不写但一定会踩的坑FFmpeg 的 API 有几个地方特别容易出问题我一个个说。5.1 返回值检查不能偷懒FFmpeg 几乎所有函数都返回 int小于 0 表示出错。很多人图省事不检查结果后面用空指针或者非法值程序直接段错误。每个可能失败的调用都要检查尤其是avformat_open_input、avformat_find_stream_info、avcodec_parameters_to_context这几个。5.2 AV_NOPTS_VALUE 的判断时间戳字段可能是AV_NOPTS_VALUE这是一个宏值是INT64_MIN。直接拿来做运算会溢出。判断方法if (stream-duration ! AV_NOPTS_VALUE) { // 安全使用 }5.3 多线程与线程安全FFmpeg 的AVFormatContext不是线程安全的。一个 context 同时只能被一个线程操作。如果你要在多线程环境里读多个文件每个线程用自己的 context别共享。5.4 内存管理的三条铁律avformat_open_input成功后必须avformat_close_input。avcodec_alloc_context3分配的 context 必须avcodec_free_context。av_packet_alloc分配的 packet 必须av_packet_free。这三条记住了能避免 90% 的内存泄漏。5.5 版本兼容性FFmpeg 的 API 在不同大版本之间会变。比如AVStream-codec在 4.0 之后被废弃改用codecpar。如果你看的教程是 2016 年写的很可能用的是老 API。查文档时注意看版本号或者直接看头文件里的注释。6. 一个完整的可编译示例把上面的东西串起来写一个能直接编译运行的小程序。它接收一个文件路径打印出容器信息和所有流的详细信息。#include stdio.h #include libavformat/avformat.h #include libavcodec/avcodec.h int main(int argc, char *argv[]) { if (argc 2) { fprintf(stderr, Usage: %s input file\n, argv[0]); return 1; } AVFormatContext *fmt_ctx NULL; int ret avformat_open_input(fmt_ctx, argv[1], NULL, NULL); if (ret 0) { char errbuf[128]; av_strerror(ret, errbuf, sizeof(errbuf)); fprintf(stderr, Open failed: %s\n, errbuf); return 1; } ret avformat_find_stream_info(fmt_ctx, NULL); if (ret 0) { fprintf(stderr, Find stream info failed\n); avformat_close_input(fmt_ctx); return 1; } printf(Format: %s\n, fmt_ctx-iformat-name); printf(Duration: %.2f s\n, fmt_ctx-duration / (double)AV_TIME_BASE); printf(Bitrate: %lld bit/s\n, (long long)fmt_ctx-bit_rate); printf(Streams: %u\n, fmt_ctx-nb_streams); AVDictionaryEntry *tag NULL; while ((tag av_dict_get(fmt_ctx-metadata, , tag, AV_DICT_IGNORE_SUFFIX))) { printf( Metadata: %s %s\n, tag-key, tag-value); } for (unsigned int i 0; i fmt_ctx-nb_streams; i) { AVStream *st fmt_ctx-streams[i]; AVCodecParameters *par st-codecpar; printf(\nStream #%u [%s]\n, i, av_get_media_type_string(par-codec_type)); printf( Codec: %s\n, avcodec_get_name(par-codec_id)); printf( Time base: %d/%d\n, st-time_base.num, st-time_base.den); if (par-codec_type AVMEDIA_TYPE_VIDEO) { printf( Resolution: %dx%d\n, par-width, par-height); printf( Pixel format: %s\n, av_get_pix_fmt_name(par-format)); AVRational fr st-avg_frame_rate; if (fr.num fr.den) printf( FPS: %.2f\n, av_q2d(fr)); } else if (par-codec_type AVMEDIA_TYPE_AUDIO) { printf( Sample rate: %d\n, par-sample_rate); printf( Channels: %d\n, par-channels); printf( Sample format: %s\n, av_get_sample_fmt_name(par-format)); } } avformat_close_input(fmt_ctx); return 0; }编译命令假设 FFmpeg 装在标准路径gcc -o probe probe.c $(pkg-config --cflags --libs libavformat libavcodec)如果pkg-config找不到就手动指定gcc -o probe probe.c -lavformat -lavcodec -lavutil跑一下./probe test.mp4输出大概是这样Format: mov,mp4,m4a,3gp,3g2,mj2 Duration: 12.34 s Bitrate: 1500000 bit/s Streams: 2 Metadata: major_brand isom Metadata: encoder Lavf58.29.100 Stream #0 [video] Codec: h264 Time base: 1/90000 Resolution: 1920x1080 Pixel format: yuv420p FPS: 30.00 Stream #1 [audio] Codec: aac Time base: 1/44100 Sample rate: 44100 Channels: 2 Sample format: fltp7. 从能读到读得准几个进阶思路基础功能跑通之后实际项目里还会遇到一些更细的需求。7.1 精确时长读最后一帧的时间戳如果duration不可靠可以手动 seek 到文件末尾附近读最后一帧的 PTS再换算成秒。这个方法对大多数本地文件有效但网络流不行。7.2 旋转角度display matrix手机拍的视频经常带旋转信息存在流的 side data 里。codecpar-width和height是编码时的尺寸实际显示可能要旋转 90 度。读取方法for (int i 0; i st-codecpar-nb_coded_side_data; i) { AVPacketSideData *sd st-codecpar-coded_side_data[i]; if (sd-type AV_PKT_DATA_DISPLAYMATRIX) { // sd-data 里是 3x3 矩阵可以算出旋转角度 } }这个在播放器里必须处理否则竖屏视频会横着显示。7.3 HDR 信息color transfer 和 primariesHDR 视频的codecpar里有color_trc、color_primaries、color_space这些字段。判断是不是 HDR看color_trc是不是AVCOL_TRC_SMPTE2084PQ或者AVCOL_TRC_ARIB_STD_B67HLG。7.4 多音轨的语言识别前面提过流的metadata里有 language 键。但有些文件不写或者写的是 ISO 639-2 的三字母代码如 chi、eng。实际项目里通常得准备一个映射表把代码转成用户能看懂的语言名。8. 实际项目里的经验教训最后分享几个我在真实项目里踩过的坑都是文档里不会写的。第一个坑网络流的超时。avformat_open_input对网络流默认没有超时如果服务器不响应程序会一直卡住。解决办法是传一个AVDictionary进去设置timeout选项AVDictionary *opts NULL; av_dict_set(opts, timeout, 5000000, 0); // 5 秒单位微秒 avformat_open_input(fmt_ctx, url, NULL, opts); av_dict_free(opts);第二个坑某些 MP4 的 moov box 在文件末尾。这种文件打开时 FFmpeg 需要 seek 到末尾读 moov如果输入是不可 seek 的流比如管道就会失败。解决办法是先用qt-faststart之类的工具把 moov 移到前面或者用AVIOContext自定义读取逻辑。第三个坑avformat_find_stream_info对某些文件很慢。它默认会读最多probesize字节的数据默认值是 5MB。对于大文件或者慢速存储这个探测过程可能要好几百毫秒。如果只是想知道基本信息可以调小probesizefmt_ctx-probesize 1024 * 1024; // 1MB但调太小可能导致探测不出编码格式得根据实际情况权衡。第四个坑字符编码。前面提过元数据可能是非 UTF-8。我遇到过一个日文文件标题是 Shift-JIS 编码直接打印全是乱码。后来用iconv转了一下才正常。如果项目面向国际用户这个必须处理。第五个坑codecpar-format对音频流是采样格式对视频流是像素格式。同一个字段名含义不同别搞混了。判断类型之后再用对应的转换函数。这些经验看起来零碎但每一个都是实际调试出来的。FFmpeg 的 API 就是这样基础流程不难难的是处理各种边界情况和异常输入。把基础流程跑通只是第一步真正让代码健壮的是对这些细节的处理。

相关新闻

操作系统隔离与系统调用全链路:从用户态到内核态的穿越

操作系统隔离与系统调用全链路:从用户态到内核态的穿越

6.S081这门操作系统课程讲到第三讲,终于开始触及最核心的部分:OS organization和system calls。前面几讲我们在用户态这边打转,fork、exec、wait这些系统调用用得很熟,但很少认真想过一个问题——你随手调一个write(),…

2026/10/16 11:04:59 阅读更多 →
多智能体协作新范式:DeepSeek Harness集成Codex与Claude Code实战

多智能体协作新范式:DeepSeek Harness集成Codex与Claude Code实战

一眼看到这次更新的标题,我先是愣了一下——把 Codex、Claude Code 直接做成官方插件,这不再是"自己改造工具链"的玩法,而是把多个编程代理放进同一个调度框架里当子智能体用。我前阵子刚把一批并行任务从单代理切到多代理协作&…

2026/10/16 11:04:59 阅读更多 →
Netty带宽饱和下的连接处理:背压、水线与实战调优

Netty带宽饱和下的连接处理:背压、水线与实战调优

刚经历了一次让人印象深刻的线上事故:某推送网关在业务高峰期整体带宽吃到接近上限,单个连接的数据还能正常处理,但新连接进来之后要么握手阶段超时,要么连进来以后迟迟拿不到响应。更诡异的是,已经建立的长连接里&…

2026/10/16 11:04:59 阅读更多 →

最新新闻

C#.NET整合微信支付宝银联支付:回调验签与统一网关实践

C#.NET整合微信支付宝银联支付:回调验签与统一网关实践

简介:面向C# .NET开发人员的多支付渠道整合方案,聚焦微信、支付宝与银联支付在统一业务后台中的接入与协同。内容覆盖微信JSAPI、Native、H5、APP,支付宝网页支付、APP支付、扫码支付,以及银联网上支付、快捷支付等典型场景&#…

2026/10/16 13:51:34 阅读更多 →
OpenCV 4.5.1环境搭建:源码编译contrib与VS2019配置完整指南

OpenCV 4.5.1环境搭建:源码编译contrib与VS2019配置完整指南

简介:面向使用 VS2019 搭建 OpenCV 4.5.1 扩展开发环境的学习者,尤其是被 CMake 配置、opencv_contrib 模块缺失反复卡住的新手。作者将耗时五天的踩坑过程整理为可直接复用的现成环境包:下载 zip 并解压后,按配套博文步骤配置&am…

2026/10/16 13:51:34 阅读更多 →
GPOPS2高精度最优控制求解原理与MATLAB实战

GPOPS2高精度最优控制求解原理与MATLAB实战

简介:本资源是面向控制工程、航空航天及优化算法研究者的GPOPS2数值优化工具包,专为解决多阶段动态优化问题设计,尤其适用于飞行器轨迹规划、机器人路径设计与航天器轨道优化等高精度建模场景,需具备MATLAB编程基础与非线性优化理…

2026/10/16 13:51:34 阅读更多 →
2026年国内AI大模型API聚合平台排名前十推荐:实测选型与避坑指南

2026年国内AI大模型API聚合平台排名前十推荐:实测选型与避坑指南

两个前提,先立规矩 这类榜单文章满天飞,先把话说明白:其一,拒绝营销套路,额度虚标、服务停摆风险、变相收费一律过滤;其二,拒绝参数堆砌,只看三样东西——真实到账价格、实际响应速度…

2026/10/16 13:51:34 阅读更多 →
从RISC-V到全国产载板:嵌入式核心板推荐中值得关注的众达科技进迭时空K3方案

从RISC-V到全国产载板:嵌入式核心板推荐中值得关注的众达科技进迭时空K3方案

国产嵌入式算力重构背景下的核心板选型困惑2026年,工业控制、边缘计算、装备信息化以及具身智能等设备端场景,正在把「嵌入式核心板」从传统配套器件推到系统架构的中心位置。过去很长一段时间里,核心板选型往往沿用x86或进口ARM生态的成熟方…

2026/10/16 13:51:34 阅读更多 →
上位机软件架构设计:分层思路与通信层实践

上位机软件架构设计:分层思路与通信层实践

上位机软件架构设计,说穿了就是解决一件事:让一台天天连设备、发指令、收数据的电脑程序,能经得起需求变化和长期维护。我做了几年的设备上位机开发,从最开始“界面上一堆按钮,背后全塞在一起”的小工具,到…

2026/10/16 13:50:33 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →