惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
O
OpenAI News
WordPress大学
WordPress大学
P
Proofpoint News Feed
J
Java Code Geeks
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Register - Security
The Register - Security
Engineering at Meta
Engineering at Meta
H
Help Net Security
人人都是产品经理
人人都是产品经理
Vercel News
Vercel News
N
Netflix TechBlog - Medium
F
Full Disclosure
U
Unit 42
Latest news
Latest news
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
InfoQ
L
LINUX DO - 最新话题
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
小众软件
小众软件
I
Intezer
V
V2EX
S
SegmentFault 最新的问题
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
Security Archives - TechRepublic
Security Archives - TechRepublic
Recent Announcements
Recent Announcements
C
Check Point Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Recorded Future
Recorded Future
博客园 - Franky
Project Zero
Project Zero
S
Securelist
Attack and Defense Labs
Attack and Defense Labs
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
NISL@THU
NISL@THU
云风的 BLOG
云风的 BLOG
S
Secure Thoughts
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed

豆沙工作室

停止 Mac 上的时间机器备份 从 Gitea 迁移到 Forgejo 评论里的私信消息 记得清理你的日志文件 招魂 Annihilation [11ty] 使用 Pagefind 实现静态站点的搜索 直面死亡 [11ty] 修复 RSS 里的数学公式 看到了一个神奇的 UA PreFound:10B - 四次击键 一点小小的 AI 震撼 PreFound:10A - 文本之外,还有(绘)文字 PreFound:09 - 色彩运算子 计算机不予申辩 Re-Game 2.0 Addendum C - Press START PreFound:08 - 动效与仿射 PreFound:07 - 图片拼贴报 PreFound:06 - 漫游雪花海 PreFound:05 - 写出到屏幕 And Trek I Will PreFound:04 - 举起渲染器 PreFound:02 - 启动编码器 PreFound:01 - 新建文件夹 PreFound:00 - 梦开始的地方 向着无穷远跑去 如何打包一个 Python 项目 只能作为光盘手册存在的书籍 航天飞机和马屁股的关系 Get an iPod 从鸿蒙系统中拉取蓝牙 HCI 日志 第二类擦除 迁移 FreshRSS 的数据库
PreFound:03 - 跨越比特面
https://archive.dsstudio.tech/person/ · 2026-05-24 · via 豆沙工作室

开始千里之行的第二个三分钟。

刚刚上期我们完成了基本的渲染循环的实现,留了两个关键问题:那个警告是从哪来的?以及怎么把 RGB 转换成 YUV420?

指定色域

警告本身的内容很简单,因为我们没有指定色域,所以它默认使用了 MPEG 色域。

如果只是需要去掉这一行警告的话,在设置编码器上下文时明确指定色域即可:

context->color_range = AVCOL_RANGE_MPEG;

比较有趣的是:这个色域是一个窄色域,其明度范围为 [16, 235], 色度范围为 [16-240]. 另一个色域格式是 AVCOL_RANGE_JPEG, 其范围为 [0, 255].

虽然看似我们应该设置 AVCOL_RANGE_JPEG, 不过真这么做的话,会发现 VLC 等播放器中的颜色反而比设置为 AVCOL_RANGE_MPEG 要更为「黯淡」——尤其是后面我们开始从 RGB 图像中复制信息的时候,你会发现黑不够黑而白不够白。

和色彩相关的问题总是无穷无尽的,比如这玩意真的叫「色域」么?它和 Gamut 的区别是什么?为什么源码里叫它 MPEG/JPEG 而不是像其他程序中称其为 Limited/Full?

这些问题只能暂时先放下了。我们的目标是用代码程序化地生成视频,这些问题暂时不影响这个主要目标。

格式转换

尽管我们可以手动写 RGB 到 YUV420 的转换,但这块石头我还是不太想亲手搬的。FFmpeg 库为我们提供了 swscale 能力,我们可以直接借用。

分配一个 RGB 的缓冲区:

uint8_t *rgbBuffer = new uint8_t[context->width * context->height * 3];

以及分配一个 swscale 的上下文:

auto* swContext = sws_getContext(context->width,
                                    context->height,
                                    AV_PIX_FMT_RGB24,
                                    context->width,
                                    context->height,
                                    context->pix_fmt,
                                    0,
                                    nullptr,
                                    nullptr,
                                    nullptr);
// 源和目标宽高一致;源格式为 RGB24, 目标格式遵循编码器设定,没有额外的 filter

有了这个上下文之后,我们就可以通过调用 sws_scale 函数来做格式之间的转换了。

先将之前写 YCbCr 空间的部分替换为写 RGB buffer:

// in for-loop
// after av_frame_make_writable
// 简单的条纹
for (int y = 0; y < context->height; y++) {
    memset(&rgbBuffer[y * context->width * 3],
            (y + i) & 0xff,
            context->width * 3);
}

写完 RGB buffer 后,就可以直接通过 swscale 转换到 frame 当中去:

// in for-loop
const uint8_t* sourceDataPlanes[1] = {rgbBuffer};
const int sourceDataStrides[1] = {3 * context->width};

err = sws_scale(swContext,
                sourceDataPlanes,
                sourceDataStrides,
                0,
                context->height,
                frame->data,
                frame->linesize);
if (err < 0) {
    spdlog::error("failed to convert frame data: {}", err);
    continue;
}

当然,申请的资源要记得释放:

sws_freeContext(swContext);
delete[] rgbBuffer;

完整的代码在这里

成果展示

output.mkv.png

图 1 output.mkv

下一步

经过这一段的折腾,我们终于搭建好了视频渲染的基础设施。接下来就是想办法开始程序化地填充 RGB Buffer 了。

程序化填充 RGB Buffer 的方法有很多,比如通过图形库即可实现无头渲染;或者如果我们确实想搞一点非常有趣的效果的话,也可以使用 OpenGL 的无窗口模式进行绘制。