author: hjjdebug
date: 2025年 07月 11日 星期五 10:51:23 CST
descrip: ffmpeg 中 write_option()函数详细注释


文章目录

  • 1. 函数原型
    • 1.1 参数说明
    • 1.2 SpecifierOpt 说明符选项结构
  • 2. write_option 代码注释
    • 2.1 谁调用了write_option 函数?
  • 3. 小结:

write_option()不仅在ffmpeg 中, ffplay,ffprobe 都调用了该函数,
write_option是处理命令行参数解析的核心函数,
负责将参数值写入目标地址(全局变量或结构体字段)
所以这里着重介绍一下:

1. 函数原型

static int write_option(void *optctx,
const OptionDef *opt,
const char *key,
const char *val)

1.1 参数说明

optctx:目标上下文(NULL时写入全局变量)
opt:选项实例指针,"选项定义OptionDef"完整定义如下

typedef struct OptionDef {const char *name; //这里的name 就是 查找时的key值, 由此找到实例指针int flags; // val的解释由flags来定义,它还有一个功能确定目标类型union { //目标位置,函数指针,偏移量共用一个union, 到底是什么由flags确定void *dst_ptr;int (*func_arg)(void *, const char *, const char *);size_t off;} u; 	 //下面2个字符串在帮助信息中使用const char *help;    //帮助的提示信息const char *argname; //参数的名称信息 } OptionDef;

key:原始命令行键字符串
由key 值找到的OptionDef 实例指针 opt, 但key在这里不是多余的,因为key可能还带有:及附加信息
val 键对应的值, 是一个字符串.
通过opt->flags 可以将其转换为多种数据类型.
OPT_BOOL: 转换为bool值
OPT_STRING:分配内存并拷贝字符串
OPT_INT64:用strtol转换成数值
OPT_OFFSET|OPT_SPEC 决定ctx中的偏移位置
目标地址由opt->u决定,可能是全局固定地址,或者optctx中的一个偏移位置,或者全局函数

举几个OptionDef 实例的例子.

    -f 选项,有参数,是OFFSET,可为输入或输出参数,偏移量OFFSET(format),帮助信息 "force format", 帮助参数名 "fmt"{ "f",              HAS_ARG | OPT_STRING | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off       = OFFSET(format) }, "force format", "fmt" }-y 选项, 布尔值, 直接给全局变量file_overwrite赋值,帮助信息:"overwrite output files", 参数名称没有设置{ "y",              OPT_BOOL,                                    {              &file_overwrite }, "overwrite output files" }-c 选项, 带参数,是字符串, 是OPT_SPEC, 可为输入或输出参数,偏移位置在OFFSET(codec_names)帮助信息是 "codec name", 帮助参数是"codec"{ "c",          HAS_ARG | OPT_STRING | OPT_SPEC | OPT_INPUT | OPT_OUTPUT,  { .off       = OFFSET(codec_names) }, "codec name", "codec" },-vcodec key,带参数是视频参数,输入或输出参数,属于文件范围.没有说明值值是什么类型,目标位置是函数{ "vcodec",       OPT_VIDEO | HAS_ARG  | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT,{ .func_arg = opt_video_codec }, "force video codec ('copy' to copy stream)", "codec" },

OPT_SPEC 的意思是说明符选项,在目标位置,存储的是一个数组指针,可以保存很多条信息.
此例codec_name 可以保留-c:v h264 -c:a aac 等, 要把"v"对应的"h264","a"对应的"aac"这些信息都保留下来
与上面类似的还有-b:v -b:a 设置等.
其实SPEC 的使用还有很多,例如 -r frame_rate; -aspect aspect 设置; -pix_fmt pixel 设置
他们都是一个键对应着若干个值. 这些值可以作为候选值.

1.2 SpecifierOpt 说明符选项结构

OPT_SEC 对应的是一个SpecifierOpt 选项数组, 这里给出 SpecifierOpt的结构定义:

typedef struct SpecifierOpt {char *specifier;    /**< stream/chapter/program/... specifier,一般是"v","a","s","d" 字符串 */union {uint8_t *str;int        i;int64_t  i64;uint64_t ui64;float      f;double   dbl;} u; } SpecifierOpt;

2. write_option 代码注释

铺垫差不多了,把代码copy 来标注一下:

static int write_option(void *optctx, const OptionDef *po, const char *opt, const char *arg)
{//确定目标地址,带OPT_OFFSET或OPT_SPEC, 地址在optctx结构内部(optctx+offset),否则是全局变量地址void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?(uint8_t *)optctx + po->u.off : po->u.dst_ptr;int *dstcount;double num;int ret;if (po->flags & OPT_SPEC) { //有SPEC 标志SpecifierOpt **so = dst;  //目标位置是一个SpecifierOpt数组的地址char *p = strchr(opt, ':'); //把key值用:分割char *str;dstcount = (int *)(so + 1); //数组地址下面so+1是一个整数地址,这里将要存储数组的大小ret = grow_array((void**)so, sizeof(**so), dstcount, *dstcount + 1);//把数组扩大1个if (ret < 0)return ret;str = av_strdup(p ? p + 1 : ""); //有:复制:后面部分,无冒号不复制(复制空)if (!str)return AVERROR(ENOMEM);
//dup的字符串就是specifier,翻译为"说明符",一般是"v""a""s""d"(*so)[*dstcount - 1].specifier = str; dst = &(*so)[*dstcount - 1].u; //调整dst 为新分配的SpecifierOpt的u位置,把值存到这里}if (po->flags & OPT_STRING) { //如果是字符串,把字符串dup,地址放到目标处char *str;str = av_strdup(arg);av_freep(dst);if (!str)return AVERROR(ENOMEM);*(char **)dst = str;} else if (po->flags & OPT_BOOL || po->flags & OPT_INT) {ret = parse_number(opt, arg, OPT_INT64, INT_MIN, INT_MAX, &num);if (ret < 0)return ret;*(int *)dst = num; //bool值或int,将参数变成数值,保存到目标} else if (po->flags & OPT_INT64) {ret = parse_number(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX, &num);if (ret < 0)return ret;*(int64_t *)dst = num; //64位,那就按64位存储} else if (po->flags & OPT_TIME) {ret = av_parse_time(dst, arg, 1); //目标存时间if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Invalid duration for option %s: %s\n",opt, arg);return ret;}} else if (po->flags & OPT_FLOAT) {ret = parse_number(opt, arg, OPT_FLOAT, -INFINITY, INFINITY, &num);if (ret < 0)return ret;*(float *)dst = num; //目标存float值} else if (po->flags & OPT_DOUBLE) {ret = parse_number(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY, &num);if (ret < 0)return ret;*(double *)dst = num; //目标存float值} else if (po->u.func_arg) { //如果实例设置了函数指针,则执行该函数int ret = po->u.func_arg(optctx, opt, arg);if (ret < 0) {av_log(NULL, AV_LOG_ERROR,"Failed to set value '%s' for option '%s': %s\n",arg, opt, av_err2str(ret));return ret;}}if (po->flags & OPT_EXIT) //选项实例要求退出,那就返回退出码return AVERROR_EXIT;return 0;
}

2.1 谁调用了write_option 函数?

1. ffplay ffprobe 有 parse_options -> parse_option ->write_option 调用
int parse_option(void *optctx, const char *opt, const char *arg, const OptionDef *options)
它们定义的OptionDef表options 基本上都是直接分析到全局变量.
optctx 就是空,没有使用.
opt 是key, arg 是value
其功能是依据options 表, 根据key值 opt,将val值 arg写入目的地址.

其再上一层parse_options() 其输入参数直接就是命令行参数. 就是循环调用parse_option
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
void (*parse_arg_function)(void , const char))
这2个函数都比较简单,就不多做分析了.
整体的功能是把命令行参数写到指定的全局变量中以供使用

2. ffmpeg 中调用 parse_optgroup() -> write_option()
int parse_optgroup(void *optctx, OptionGroup *g)
这一次较为复杂,因为它使用了optctx, 不再是空了,
其2引入了OptionGroup 概念. 什么是OptionGroup? 就是选项组.
我们看看它的简化定义,忽略暂时未使用部分.

typedef struct OptionGroup {Option *opts;  //OptionGroup 就是 Option 动态数组,Option就是3个指针int  nb_opts;... //忽略暂时不关注部分
} OptionGroup;

parse_optgroup()功能也很好理解, write_option 写了一条option,
parse_optgroup就是循环调用write_option, 把group中所有的option都写出去.

那OptionGroup 从哪里来? 谁调用了parse_optgroup? 有2处
在ffmpeg_parse_options() 函数中, 有调用分析选项组函数parse_optgroup,全局选项组为参数,
这些分析的结果都为写到全局变量中,比较简单

int ffmpeg_parse_options(int argc, char **argv)
{...ret = parse_optgroup(NULL, &octx.global_opts);ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);...
}

还有一处,在打开文件的时候. 实际是2次,打开输入文件列表,打开输出文件列表
static int open_files(OptionGroupList *l, const char inout,
int (open_file)(OptionsContext, const char
))
看参数我们知道,它又引入了2个概念. 先看第一个OptionGroupList

typedef struct OptionGroupList {const OptionGroupDef *group_def; //不重要,主要是名称,分割符等OptionGroup *groups; //选项组的数组int       nb_groups;
} OptionGroupList;

既然是选项组的数组,那就循环调用parse_optgroup(),把结果存到OptionsContext 结构中 o
一个文件对应一个选项组,多个文件对应多个选项组,多个选项组组织成一个表叫选项组列表

这个函数的关键是调用回调函数open_file,传递参数OptionContext &o 和 文件名g->arg
ret = open_file(&o, g->arg);
那OptionsContext 是什么结构呢?
这个很复杂,就不用copy了,它是ffmpeg 特有的,ffplay,ffprobe 都不使用这个结构.
该context定义了ffmpeg options中所有的选项值的存储位置, 并为后面的open_file服务.
这就是ffmpeg 为什么命令行参数多样化的来历.

3. 小结:

  1. 选项key/value的引入
    本来选项用argv[i]访问挺好的.
    但是由于参数变得复杂,我们引入了key/value的概念.
    只所以引入key, 就是因为这样val不再依赖位置i来确定了,而是依赖key来确定.
    例如原来是arg[3]为输入文件名, 现在规定-i 选项后面跟文件名,
    显然后者更灵活.

  2. 选线组的引入
    一个选项只能说明一个属性,多个选项说明多个属性,所以选线组是很自然的. 一个文件对应一个选项组

  3. 选项组列表的引入
    一个文件对应一个选项组,那多个文件对应多个选项组,把多个"选项组"组织成表叫选项组列表,
    我们把多个文件按输入文件,输出文件分组. 则可以分成2个文件组,
    输入文件组对应输入选项组列表,
    输出文件组对应输出选项组列表,

好了,有了这些概念,您大概可以读懂ffmpeg 的ffmpeg_parse_options 函数了.
至于它的split_commandline()及open_files() 这里就不展开了. 保持简洁和高效!
读懂了ffmpeg_parse_options函数,就读懂了框架的1/2.

作为实战,不妨分析一下下面还算简单的命令行参数
ffmpeg -v debug -c:v h264 -i ./1.ts -f null -

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914062.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914062.shtml
英文地址,请注明出处:http://en.pswp.cn/news/914062.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PandaCoder重大产品更新-引入Jenkinsfile文件支持

写在前面 安装这个插件可以直接平替 Jenkinsfile Pro &#xff0c;节省200元关于插件介绍的处女篇&#xff1a;https://mp.weixin.qq.com/s/fwMEhmx8vxVlvfnipx09Ag为什么叫「熊猫编码助手」&#xff1f; 熊猫是中国的国宝&#xff0c;备受世界喜爱&#xff0c;代表着中国特色和…

链表算法之【判断链表中是否有环】

目录 LeetCode-141题 LeetCode-141题 给定一个链表的头节点&#xff0c;判断链表中是否存在环 class Solution {public boolean hasCycle(ListNode head) {// checkif (head null || head.next null)return false;// 定义两个指针&#xff0c;一个快指针[fast]&#xff0c…

Ubuntu 22.04安装SQL Server指南

看起来在安装过程中出现了问题&#xff0c;导致 mssql-server 没有正确安装。以下是排查和修复步骤&#xff1a;1. 检查是否成功安装了 mssql-server 运行以下命令&#xff0c;确认是否已安装&#xff1a; dpkg -l | grep mssql-server如果没有任何输出&#xff0c;说明 mssql-…

Vue+ElementUI聊天室开发指南

Hi&#xff0c;我是布兰妮甜 &#xff01;在现代Web应用中&#xff0c;实时聊天功能已成为许多社交平台、协作工具和客户支持系统的核心需求。本文将详细介绍如何使用Vue.js框架配合ElementUI组件库实现一个功能完整的聊天室应用。我们将从项目搭建开始&#xff0c;逐步实现用户…

提升你的AI交互技能:使用Anthropic互动提示教程

探索Anthropic的互动式提示工程教程&#xff1a;让Claude与你更默契 在当今人工智能世界中&#xff0c;熟练掌握有效的提示工程成为了与AI进行高效沟通的关键。Anthropic推出了一款全面且互动性强的教程&#xff0c;名为“Prompt Engineering Interactive Tutorial”&#xff0…

从 JavaFX WebView 迁移至 JxBrowser

长久以来&#xff0c;JavaFX 一直包含一个内置的 WebView 组件&#xff0c;这是在 Java 应用中渲染 Web 内容的一个稳定方案。然而&#xff0c;在更复杂或要求更高的使用场景中&#xff0c;它可能就不够用了。因此&#xff0c;许多开发者转向了像 JxBrowser 这样的替代方案。 …

将 Go 应用从 x86 平台迁移至 Amazon Graviton:场景剖析与最佳实践

简介 近年来&#xff0c;Amazon Graviton 处理器以其优越的性价比和强劲的性能&#xff0c;成为了构建高效、可扩展云原生应用的重要选择。Graviton 采用基于 Arm64 架构的芯片&#xff0c;与传统的 x86 架构相比存在不少架构差异。虽然 Go 天生对 Arm64 具有良好支持&#xf…

arcgis api for js 设置地图服务请求带有请求头信息

通过地图的config模块的请求拦截器来设置请求头信息&#xff0c;如下示例&#xff1a; 1、引入&#xff1a;‘esri/config’ 1、设置请求头信息 import { loadArcgisModules } from /utils/map/mapLoadUtil export default { mounted() {this.loadMap()}, methods: {/** ****…

工业通信升级新选择:耐达讯CCLINKIE转Modbus TCP网关

在工业自动化系统中&#xff0c;协议转换网关的选择直接影响系统稳定性与通信效率。对于CCLINKIE转Modbus TCP场景&#xff0c;耐达讯通信技术网关凭借以下特性&#xff0c;成为多个项目中的优选方案。技术选型要点协议兼容性支持CCLINKIE的令牌环机制与Modbus TCP的TCP/IP协议…

使用python的 FastApi框架开发图书管理系统-前后端分离项目分享

今天给大家分享一个 我最近使用python 框架 fastapi 写的一个web项目 &#xff0c;叫图书管理系统。项目主要是来巩固 python的编程技术。使用的是前端后 分离开发。 主要实现的功能&#xff1a; 1、用户管理&#xff1a;可以新增、编辑、删除用户信息。 2、图书管理&#xff1…

上位机知识篇---Docker

Docker 详细介绍 一、Docker 是什么 Docker 是一个开源的容器化平台&#xff0c;它允许开发者将应用程序及其依赖项打包到一个标准化的单元&#xff08;称为容器&#xff09;中&#xff0c;确保应用在任何环境中都能以相同的方式运行。 简单来说&#xff0c;Docker 解决了 &…

蓝桥杯第十六届(2025)真题深度解析:思路复盘与代码实战

> 省一选手的血泪经验:**避免这些坑,你也能冲进国赛!** 2025年蓝桥杯省赛已落下帷幕,作为近年来**难度最高的一届竞赛**,不少选手在考场上遭遇了“滑铁卢”。本文将以C++ B组真题为例,逐题解析解题思路,并提供**优化后的AC代码与详细注释**。笔者最终排名省一前40%,…

使用gdal读取shp及filegdb文件

一、使用qgis开源工具构建两个文件&#xff0c;分别是filegdb和shp&#xff0c;每个文件包含一个图层&#xff0c;图层内容只包含一个字段&#xff1a;id&#xff0c;有两个数据行&#xff0c;图层几何为多边形&#xff0c;图层都是如下的效果。二、使用rust读取上述文件 rust依…

从0开始学习R语言--Day44--LR检验

之前我们提到用LM检验的方式&#xff0c;来判断数据在空间上是否受到邻近数据及其残差的影响&#xff0c;但是LM检验是采用直接计算的方式&#xff0c;只关注了数据的残差平方和&#xff0c;没有数据关于依赖项的考虑&#xff0c;容易被结果误导。而LR检验虽然在结果上有时候跟…

openEuler 24.03 (LTS-SP1) 下私有镜像仓库部署与自签 SSL 全流程目标

目录 openEuler 24.03 (LTS-SP1) 下私有镜像仓库部署与自签 SSL 全流程 1 创建根 CA 与服务器证书&#xff08;修正版&#xff1a;SAN 写法兼容所有 OpenSSL&#xff09; 2 配置 Docker Compose 文件 3 客户端节点信任 CA 3.1 Docker 3.2 containerd 4 推送 / 拉取测试 …

mysql的LIMIT 用法

常见用法1. 限制返回行数-- 返回前5条记录 SELECT * FROM products LIMIT 5;2. 分页查询&#xff08;带偏移量&#xff09;-- 跳过前10条&#xff0c;返回接下来的5条记录&#xff08;第11-15条&#xff09; SELECT * FROM products LIMIT 10, 5;-- MySQL 8.0 也支持这种语法 S…

maven 发布到中央仓库之持续集成-03

maven 系列 maven-01-发布到中央仓库概览 maven-02-发布到中央仓库常用脚本 maven-03-发布到中央仓库之持续集成 maven-04-发布到中央仓库之 Ignore Licence maven-05-maven 配置进阶学习 maven-06-maven 中央仓库 OSSRH 停止服务&#xff0c;Central Publishing Portal …

(补充)RS422

RS4221. 基本定义与定位 官方名称&#xff1a; EIA/TIA-422&#xff08;电子工业协会/电信工业协会标准422&#xff09;。类型&#xff1a; 一种定义了电气特性的 平衡式差分 串行通信标准。目的&#xff1a; 克服 RS-232 在传输距离、速率和抗干扰能力上的严重局限性。核心思想…

自建ELK vs 云商日志服务:成本对比分析

在当今数据驱动的时代&#xff0c;日志管理已成为企业IT基础设施中不可或缺的一部分。面对日益增长的日志数据&#xff0c;许多团队都在纠结&#xff1a;是自建ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;堆栈&#xff0c;还是直接使用云服务商提供的日志服务…

Eigen 几何模块深拆:Isometry3d vs Affine3d + 变换矩阵本质详解

文章目录0 写在前面1 数学背景对比2 Eigen 实现差异3 Isometry3d 是不是 4 4 矩阵&#xff1f;4 核心 API 速查5 实战示例5.1 SLAM 位姿链&#xff1a;相机点 → 世界点5.2 体素滤波&#xff1a;各向异性缩放&#xff08;X/Y → 5 cm&#xff0c;Z → 10 cm&#xff09;5.3 把…