ffmpeg命令分析-r

《FFmpeg原理》的社群来了,想加入社群的朋友请购买 VIP 版,VIP 版有更高级的内容与答疑服务。


本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8

之前的文章分析 FFMpeg 工程的 do_video_out() 函数的时候,建议不关注 delta0deltanb0_framesnb_frames 等变量。

因为在之前的命令没有用帧率变换参数,-r 。所以上面这些变量赋值,有跟没有是一样的。

现在来补一下之前缺失的内容。命令行指定 -r 之后,delta0deltanb0_framesnb_frames 的变化。

本文章主要讲解 FFMpeg 里面是如何实现帧率变换的,例如 24fps 是如何转成 8fps的,缩小了3倍的帧率。

./ffmpeg -i a.mp4 -r 8 output.flv 本文以此命令讲解,原始视频是24fps,转成 8fps。

a.mp4 下载链接:百度网盘 ,提取码:nl0s


如果没指定 -r ,就会从 buffersink 获取输出的帧率,帧率跟输入文件帧率一样,代码如下:

如果命令行指定了 -r ,就会用命令行参数赋值 给 ost->frame_rate。,代码如下:


上面是 命令行参数 -r 赋值给 ost->frame_rate。然后 ost->frame_rate 会作为时间基赋值给 编码器的time_base,见下图代码:


所以,综上所述,-r 最后主要影响的是 编码器的time_base,那编码器的time_base是怎么影响帧率变换的呢?请看下面。

在讲之前,需要普及一个知识点,请看下面代码

int a = av_rescale_q(1,  (AVRational){1, 24}, (AVRational){1, 12});
int b = av_rescale_q(2,  (AVRational){1, 24}, (AVRational){1, 12});

这里 a 跟 b 都是1,a不会是0.5。av_rescale_q() 返回的整数,所以精度丢失了。这也是为什么 reap_filtes() 内部会搞一个 float_pts 出来,这个float_pts 是有精度的。请看下图,编码器的time_base 的影响就在这里。

可以看到,上图代码把原始视频帧 的pts 的时间基转成 编码器的time_base了。这里是一个什么概念呢。

假如原始视频帧率是每秒24帧,他AVFrame的pts的时间基也是 {1,24}。那这时候这个 filtered_frame->pts 肯定是1,2,3,4 这样递增下去的。

把 filtered_frame->pts 转成编码器时间基 {1,8} 会怎样?

播放时间pts不变,1/24 = 0.041s ,第一帧在 0.041s 播放。然后 x/8 = 0.041,这个x等于多少呢?x = 0.333。

也就是说,第二帧的 filtered_frame->pts 原本是 1,时间基从 {1,24} 转成 {1,8} 之后,pts 从1 变成了 0.333。

类推,第三帧pts变成了 0.666,第四种变成0.999。

然后,这些跟帧率变换有什么关系?请继续看下面分析。

因为帧率变换涉及到好几个场景,这里只介绍 format_video_sync = VSYNC_VFR 的场景

接着分析,虽然上面说到,原始pts转换之后变成了 0.33,0.66 之类的小数。但是对于输入文件,对于编码器来说,编码器的帧率是 8fps,{1,8},传递给编码器的frame的pts,肯定也必须是 1,2,3,4 这样的整数递增的。这里实现这个功能的是 ost->sync_opts 变量。

要好好分析一下 ost->sync_opts 这个变量,这个变量的初始值是 0,每输入一个frame给编码器,ost->sync_opts 就会+1。

如上图所示,do_video_out() 的 in_picture 帧的pts会被 ost->sync_opts 替换,达到 输入给编码器的frame pts 都是 1,2,3,递增的目的。

接下来继续讲,上面那些0.33,0.66小数是用来干嘛的?其实这些小数就是用来丢帧,实现帧率缩小的功能,或者重复上一帧,实现帧率变大功能。

如下图所示,nb0_frames 这个变量不用管,在format_video_sync = VSYNC_VFR 的场景 下,nb0_frames 总是0。

第一帧全是0,看不出上面的逻辑,直接从第二帧开始分析。第二帧 do_video_out() 传递的 sync_ipts 是0.333。但是此时 sync_opts 是 1。

大家可以仔细琢磨一下那句英文注释,老外写注释都比较简洁。

delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output.

delta0 = 0.333 - 1 = -0.666,delta0 代表当前输入帧与输出给编码器的时间差距,在第二帧的时候,时间差是 0.666。解析到这里,应该有点眉目,缩小帧率,肯定要根据时间差距来丢弃某些帧。

说实话,他这个算法有点复杂,我也不太明白他为什么不 sync_ipts < sync_opts 就直接丢弃,这样不是更简单?这个问题先不纠结,接着分析ffmpeg 是如何用delta0 ,delta 实现丢帧的。补充:直接 sync_ipts < sync_opts 不行,会出错。

这个算法涉及到不同刻度表的转换,用两个刻度表来解释这个算法会更明白。

delta0 = sync_ipts - ost->sync_opts; 
delta  = sync_ipts - ost->sync_opts + duration;

可以看到,因为 ost->sync_opts 每次都会 +1 地递增,而 sync_ipts 每次只能 +0.3 递增。所以delta会负得越来越大,duration是固定是 0.33。所以delta也会负得越来越大,然后 delta <= -0.6 就会把 nb_frames 置为 0 ,导致后面的for循环没执行,实现丢帧。至于为什么是0.6我也不知道。实在没想好怎么表达他这个算法逻辑,反正代码逻辑它就是这么跑的,delta越来越大,就丢帧。丢帧后,ost->sync_opts 不会+1,sync_ipts 就会慢慢赶上 sync_opts

case VSYNC_VFR:
    if (delta <= -0.6){
        //丢弃 frame
        nb_frames = 0;
    }
    else if (delta > 0.6)
        ost->sync_opts = lrint(sync_ipts);
    break;

实际丢帧情况如下。

0 0.33 0.66(x) 1(x) 1.33 1.66(x) 2(x) 2.33 2.66(x) 3(x)

只有 .33 后缀的帧才会保留,确实是缩小了3倍帧率。

实际上,他这个算法应该是一个数学公式,sync_ipts + duration + 60% 刻度 > sync_opts,如果大于 sync_opts就可以 输出给解码器,小于就丢弃。这里的刻度是1,所以60%刻度是0.6。sync_ipts + duration 是因为只要这个frame的区间跨过 sync_opts 刻度,哪怕跨过一点点,都可以输出。


如果不改变帧率,sync_opts 跟 sync_ipts 是同步+1的,duration也是1,然后 delta0 一直是一个非常小的接近0的数字,delta 一直是接近1的数字。

所以不改变帧率,delta0 跟delta 这些变量是没有作用的。


接下来继续分析 帧率放大算法是如何实现的。

把 帧率 {1,24} 转成 {1,48},实际上就是把 pts 乘以 2 。

注意,有些MP4 是 VSYNC_CFR,有些是 VSYNC_VFR。

CFR 的帧率翻倍,会插入新帧,文件大小也会翻倍。

VFR 的帧率翻倍,不会插入新帧,文件大小不变。

 case VSYNC_CFR:
            // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c
            if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) {
                nb_frames = 0;
            } else if (delta < -1.1)
                nb_frames = 0;
            else if (delta > 1.1) {
                nb_frames = lrintf(delta);
                if (delta0 > 1.1)
                    nb0_frames = lrintf(delta0 - 0.6);
            }
            break;
case VSYNC_VFR:
            if (delta <= -0.6){
                //丢弃 frame
                nb_frames = 0;
            }
            else if (delta > 0.6)
                ost->sync_opts = lrint(sync_ipts);
            break;

实际上,我个人认为,命令行 参数 -r 在 ffmpeg.c 里面的实现是一个历史遗留问题,这种实现在 ffmpeg.c 里面暴露了太多的复杂性,实际上新版本的ffmpeg,例如 4.4 版本,已经有 fpsframerate 两个新的滤镜来实现帧率转换。

所以,调 API 函数实现帧率转换,推荐使用 fpsframerate 滤镜,就没有这么多 delta 变量之类的。


由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。

<think>好的,我现在要分析用户提供的FFmpeg命令,并解释每个参数的作用。首先,我需要回忆一下FFmpeg的基本结构和常见参数。用户给出的命令是: ffmpeg -y -v error -i 1.mp4 -r 25/1 -b:v 360000 -vf scale=630:-1,pad=ceil(iw/2)*2:ceil(ih/2)*2 -max_muxing_queue_size 1024 1_new.mp4 首先,我会拆分每个参数,逐一理解其含义。可能需要注意参数的位置和顺序,因为FFmpeg的参数顺序有时会影响处理流程。 1. **-y**:这个参数通常用于覆盖输出文件而不提示确认。如果输出文件已经存在,FFmpeg会直接覆盖,不需要用户确认。所以在命令中加上-y可以避免执行时被询问是否覆盖。 2. **-v error**:设置日志的详细级别为error,这意味着只有错误信息会被输出,其他信息如警告或调试信息会被抑制。这样可以让输出更简洁,只显示关键问题。 3. **-i 1.mp4**:指定输入文件为1.mp4。-i是输入参数,后面紧跟文件名。 接下来是输出参数的配置: 4. **-r 25/1**:设置输出视频的帧率为25帧每秒。25/1等于25,可能写成25更直接,但这里用了分数形式。这个参数影响输出视频的流畅度,尤其是当原始视频帧率不同时,FFmpeg可能会进行帧的复制或丢弃。 5. **-b:v 360000**:设置视频的比特率为360000比特/秒,即360kbps。比特率影响视频的质量和文件大小,较低的比特率可能导致质量下降,但文件更小。 6. **-vf**:这是视频过滤器选项,后面跟着的是一系列过滤器链,用逗号分隔。这里的过滤器有两个部分: a. **scale=630:-1**:将视频宽度缩放为630像素,高度按比例自动计算,保持原始宽高比。这里-1表示自动计算,但需要注意的是,FFmpeg要求高度必须是偶数,否则可能会报错。不过后续的pad过滤器可能处理了这个问题。 b. **pad=ceil(iw/2)*2:ceil(ih/2)*2**:这个pad过滤器的作用是调整视频的宽度和高度,确保它们都是偶数。因为某些编码器要求分辨率必须是偶数,否则会出错。ceil(iw/2)*2的意思是,将当前宽度(iw)除以2,向上取整后再乘以2,这样即使原宽度是奇数,也会变成最近的偶数。例如,如果缩放后的宽度是630,高度是某个奇数,经过这个pad处理后,宽度和高度都会被调整为偶数。pad默认会用黑色填充额外的区域,但这里没有指定颜色,可能使用默认的黑色。 7. **-max_muxing_queue_size 1024**:这个参数用于调整复用队列的大小。在某些情况下,当处理高分辨率或高比特率的视频时,可能会出现“Too many packets buffered”的错误,增加这个值可以缓解这个问题。默认值可能较小,设为1024增大队列容量,避免写入输出文件时出现问题。 最后,输出文件是**1_new.mp4**,即转换后的视频保存为此文件名。 现在需要验证每个参数的正确性,尤其是视频过滤器部分。例如,scale=630:-1可能会计算出高度,但pad后的ceil处理是否必要?因为scale可能已经导致高度为偶数,但可能在某些情况下,比如原始视频的宽高比导致计算后的高度为奇数,这时候pad过滤器就能确保最终尺寸是偶数。例如,如果原视频宽高比为16:9,宽度630,那么高度应该是630*(9/16)=354.375,四舍五入可能变成354,是偶数,但如果有其他比例,可能会出现奇数。因此,pad过滤器在这里作为保险措施,确保无论scale后的尺寸如何,最终都是偶数。 另外,-r 25/1的设置可能会影响视频的播放速度,特别是当输入视频的帧率不是25时。例如,如果输入是30fps,输出25fps,FFmpeg默认会丢帧或者进行帧混合,这可能导致视频时长不变但流畅度变化。但用户可能希望强制输出为25fps,无论原始帧率如何。 最后,检查命令是否存在潜在问题,例如是否所有参数都正确组合,特别是视频过滤器链的顺序是否正确。scale在前,pad在后,这样先缩放,然后调整尺寸为偶数,是正确的顺序。 总结来说,这个命令的作用是将输入视频1.mp4转换为输出视频1_new.mp4,覆盖已有输出文件,仅输出错误日志,设置帧率25fps,视频比特率360kbps,缩放宽度至630像素并保持比例,同时确保分辨率是偶数,增加复用队列大小以避免缓冲问题。</think>让我们逐步解析这个FFmpeg命令的作用和参数含义: ```bash ffmpeg -y -v error -i 1.mp4 -r 25/1 -b:v 360000 \ -vf scale=630:-1,pad=ceil(iw/2)*2:ceil(ih/2)*2 \ -max_muxing_queue_size 1024 1_new.mp4 ``` ### 一、基础参数解析 1. **`-y`** 自动覆盖输出文件(无需确认),若存在同名文件直接替换。 2. **`-v error`** 设置日志级别为仅显示错误信息,屏蔽冗余输出。 3. **`-i 1.mp4`** 指定输入文件为`1.mp4`。 --- ### 二、视频处理参数 4. **`-r 25/1`** 强制输出视频帧率为$25$帧/秒。若输入帧率不同,会通过重复或丢弃帧实现。 5. **`-b:v 360000`** 设置视频码率为$360 \text{ kbps}$(即$360,000$比特/秒),控制输出文件大小与画质平衡。 --- ### 三、关键视频滤镜(`-vf`) 6. **`scale=630:-1`** 将视频宽度缩放到$630$像素,高度按比例自动计算(`-1`表示保持宽高比)。 $\text{示例}$:若原视频为$1280 \times 720$,缩放后尺寸为$630 \times 354$(计算:$630 \times (720/1280) \approx 354$)。 7. **`pad=ceil(iw/2)*2:ceil(ih/2)*2`** 确保分辨率宽高均为偶数(某些编码器强制要求): - $\text{ceil(iw/2)*2}$:宽度向上取最近的偶数 - $\text{ceil(ih/2)*2}$:高度同理 $\text{示例}$:若缩放后尺寸为$630 \times 355$,则会被补黑边至$630 \times 356$。 --- ### 四、高级参数 8. **`-max_muxing_queue_size 1024`** 增大复用器队列大小至$1024$,避免因高码率或复杂操作导致的`Too many packets buffered`错误。 --- ### 五、输出文件 9. **`1_new.mp4`** 最终生成的输出文件名。 --- ### 完整功能总结 此命令将`1.mp4`转换为以下规格的新视频: - 强制$25$帧/秒 - 码率$360$kbps - 宽度固定为$630$像素(高度自适应) - 分辨率宽高强制为偶数 - 优化处理稳定性 - 输出文件为`1_new.mp4` 适用场景:适配特定分辨率要求且需兼容性编码的视频转码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Loken2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值