使用 FFMPEG 从 FPS 不正确的视频和包含每帧时间戳的文件创建具有正确 FPS 的视频

2023-11-30

我有一个从网络摄像头捕获并在 python 中使用 OpenCV 的视频文件。网络摄像头标称 FPS 为 30 FPS,但由于环境的原因,实际 FPS 有所不同,有时可能低至 24 FPS。录制的视频是使用 OpenCV 创建的VideoWriter,使用 MP4V FOURCC 并且始终具有 30 的 FPS 值,如果实际 FPS 不是 30,这会使视频的持续时间不正确。我有一个文件,其中包含从网络摄像头读取每个帧时的时间戳(使用 python 生成)time.time()).

问题:
使用 FFMPEG(或其他软件)我可以使用时间戳信息创建新的视频文件(可能是 VFR 文件),然后将其转换为 CFR 文件吗?

我不确定创建具有正确时基的视频文件的正确方法是什么。 也许我可以分割视频的帧并将它们保存为图像,然后使用时间戳和图像来创建 VFR 视频,但我想看看是否可以用另一种更优雅的方式来做到这一点。

提前致谢!


我们可以使用修改时间戳PyAV使用称为“重新复用”的过程。
“重新复用”是用于替换流的容器而不重新编码流的术语。

例如,假设视频文件类型是MP4,并且该文件包括用H.264视频编解码器编码的单个视频流。
我们可以替换或修改 MP4 容器,而不对视频进行解码和编码。
由于时间戳是容器的一部分,我们可以使用重新复用过程来修改时间戳(与重新编码相比,主要优点是视频质量可以完美保留)。

重新复用解决方案基于以下代码示例PyAV 文档


为了进行测试,我们可以首先使用 FFmpeg CLI 创建一个简短的合成 MP4 视频文件(10 帧,1fps):

ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=10 -vcodec libx264 -crf 10 -pix_fmt yuv444p input.mp4

我们可以使用 FFprobe 查看时间戳:

ffprobe -show_packets input.mp4

Output:

pts=0
pts_time=0.000000
dts=-32768
dts_time=-2.000000
duration=16384
duration_time=1.000000
...
pts=65536
pts_time=4.000000
dts=-16384
dts_time=-1.000000
duration=16384
duration_time=1.000000
...
pts=32768
pts_time=2.000000
dts=0
dts_time=0.000000
duration=16384
duration_time=1.000000
...
pts=16384
pts_time=1.000000
dts=16384
dts_time=1.000000
duration=16384
duration_time=1.000000
...
pts_time=3.000000

可以看到,DTS时间戳是依次增加的,但是从-2.
PTS 时间戳计数 0, 4, 2, 1, 3...(非单调计数的原因是使用B-Frames)

我们还可以看到原始时间戳以每秒 16384 个刻度为单位(1/16384 是 MP4 容器使用的时基)。

所有帧的持续时间均为 1 秒(16384 个刻度)。


为了进行测试,我们将使用包含 10 个时间戳的列表(以秒为单位):

new_pts_list = [0.0, 1.0, 5.0, 7.0, 8.0, 18.0, 19.0, 20.0, 21.0, 22.0]

大间隙用于测试。


计算列表中时间戳的索引:

由于 PTS 时间戳不是连续的(并且 DTS 不是从 0 开始),因此我们必须计算应用时间戳的帧的索引。
当我们有了索引后,我们可以从列表中获取新的时间戳。

通过数据包的PTS计算索引:

index_of_old_pts = int(np.round(float(packet.pts) / float(packet.duration)))

通过数据包的DTS计算索引:

index_of_old_dts = int(np.round(float(packet.dts) / float(packet.duration)))

从列表中获取更新的时间戳:

new_pts = new_pts_list[index_of_old_pts]
new_dts = new_pts_list[index_of_old_dts]

注意:索引计算仅适用于恒定帧速率输入视频。


获得更新的时间戳后,我们必须将它们从秒转换为时基刻度单位,并修改数据包的时间戳:

new_pts_in_timebase_units = int(np.round(new_pts / packet.time_base))
new_dts_in_timebase_units = int(np.round(new_dts / packet.time_base))
packet.pts = new_pts_in_timebase_units
packet.dts = new_dts_in_timebase_units

为了提高时间戳的准确性,我决定不依赖输入的时基,并将输出的时基设置为 1/1000000 秒。

out_video_stream.time_base = Fraction(1, 1000000)

代码示例:

import av
import numpy as np
from fractions import Fraction

# Build 1 fps input file using FFmpeg CLI (for testing):
# ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=10 -vcodec libx264 -crf 10 -pix_fmt yuv444p input.mp4

input_video_file = 'input.mp4'
output_video_file = 'output.mp4'

# List of new PTS in unit of seconds (set long gaps for testing)
new_pts_list = [0.0, 1.0, 5.0, 7.0, 8.0, 18.0, 19.0, 20.0, 21.0, 22.0]

average_out_frame_period = np.mean(np.diff(np.array(new_pts_list)))  # Compute the average frame period (used for negative DTS).

# https://pyav.org/docs/develop/cookbook/basics.html#remuxing

with av.open(input_video_file, 'r') as inp:
    inp_video_stream = inp.streams.video[0]  # Get video stream - assume the input has only one stream, and that stream is a video stream.
    with av.open(output_video_file, 'w', format="mp4") as out:  # Open output file, set format to mp4
        out_video_stream = out.add_stream(template=inp_video_stream)  # Add the input stream to the output container.
        out_video_stream.time_base = Fraction(1, 1000000)  # Set the timebase to 1/1000000 for improving accuracy.

        for i, packet in enumerate(inp.demux(inp_video_stream)):  # Demux the input video stream
            if packet.dts is None:
                continue  # When DTS = None,  marks that the packet should be ignored.

            packet.stream = out_video_stream
            old_timebase = packet.time_base
            packet.time_base = out_video_stream.time_base  # Set the timebase to 1/1000000 for improving accuracy.

            index_of_old_pts = int(np.round(float(packet.pts) / float(packet.duration)))  # Convert the PTS from time-base ticks to frame index of that PTS.

            if index_of_old_pts < 0 or index_of_old_pts >= len(new_pts_list):
                new_pts = index_of_old_pts*average_out_frame_period  # Negative PTS (not supposed to exist) - use average frame period for setting the new PTS.
            else:
                new_pts = new_pts_list[index_of_old_pts]  # Get the value by the index of PTS in new_pts_list.

            new_pts_in_timebase_units = int(np.round(new_pts / packet.time_base))  # Convert from second to units of time base
            packet.pts = new_pts_in_timebase_units  # Update the PTS of the packet

            index_of_old_dts = int(np.round(float(packet.dts) / float(packet.duration)))  # Convert the DTS from time-base ticks to frame index of that DTS.
            if index_of_old_dts < 0 or index_of_old_dts >= len(new_pts_list):
                new_dts = index_of_old_dts*average_out_frame_period  # Negative DTS - use average frame period for setting the new DTS.
            else:
                new_dts = new_pts_list[index_of_old_dts]  # Get the value by the index of PTS in new_pts_list.

            new_dts_in_timebase_units = int(np.round(new_dts / packet.time_base))  # Convert from second to units of time base
            packet.dts = new_dts_in_timebase_units  # Update the DTS of the packet

            #packet.duration = new_packet_duration_in_timebase_units  #attribute 'duration' of 'av.packet.Packet' objects is not writable

            out.mux(packet)

使用 FFprobe 测试输出:

ffprobe -show_packets output.mp4

Output:

pts=0
pts_time=0.000000
dts=-4666667
dts_time=-4.666667
duration=1000000
duration_time=1.000000
...
pts=7000000
pts_time=7.000000
dts=-2333333
dts_time=-2.333333
duration=1000000
duration_time=1.000000
...
pts=5000000
pts_time=5.000000
dts=0
dts_time=0.000000
duration=1000000
duration_time=1.000000
...
pts=1000000
pts_time=1.000000
dts=1000000
dts_time=1.000000
duration=1000000
duration_time=1.000000
...
pts=6000000
pts_time=6.000000
dts=5000000
dts_time=5.000000
duration=1000000
duration_time=1.000000
...
pts=17000000
pts_time=17.000000
dts=6000000
dts_time=6.000000
duration=1000000
duration_time=1.000000
...

如您所见,时间戳与给定列表匹配。

请注意,我们无法修改duration数据包的长度,因为持续时间存储在 H.264 视频流中,而不是 MP4 容器中。
PyAV 不支持修改帧持续时间而不重新编码(但使用现代视频播放器播放视频时应该不成问题)。


动画 GIF 输出示例:

enter image description here

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 FFMPEG 从 FPS 不正确的视频和包含每帧时间戳的文件创建具有正确 FPS 的视频 的相关文章

随机推荐

  • 如何从java中的JSONObject获取所有键/子键?

    这是我的 JSONObject per page 3 total 12 data color 98B2D1 year 2000 name cerulean id 1 pantone value 15 4020 color C74375 ye
  • 堆栈中的动态数组?

    它是否正确 这是用 g 3 4 成功编译的 int main int x 12 char pz x 这是所有其他问题的组合答案 你现在的代码是not标准C 它is标准C99 这是因为 C99 允许您以这种方式动态声明数组 澄清一下 这也是标
  • 五个按钮保持等距圆周旋转

    我想在半径为 100 的圆 以 120 120 为中心的圆 实际上是正方形视图的中心 即 240 240 的圆周上旋转五个按钮 是否可以这样做 与按钮进行交互它们是旋转的且外观正确 我努力了 x round cx redious cos a
  • Nullable 和 Int32 类型之间未定义 Equal

    我正在编写一个无聊的应用程序来管理患者及其临床病史 我将 SQLite 与 DbLinq 库和 DbMetal 代码生成实用程序结合使用 以下是从底层数据库中提取的生成代码中的两个类 Table Name main Patients pub
  • 比较两个图像以检查它们是否相同

    我有一个带有 ImageView 的配置文件视图 用户可以在其中更改他们的图片 我正在保存我的新旧图像以进行比较 我想知道它们是否相同 所以如果是 我不需要将新的推送到我的服务器 我尝试了这个 但它并没有真正起作用 NSData retur
  • 为 tomcat jndi 连接到 postgresql 提供证书

    我想使用 jndi 从 tomcat 中的 servlet 连接到 postgresql 服务器 我已使用将服务器证书添加到信任库中keytool keystore usr lib jvm java 11 openjdk amd64 lib
  • 跨域资源共享 GET:“拒绝从响应中获取不安全标头“etag””

    没有自定义标头的简单 GET 请求 响应按预期返回 正文中的数据是可访问的 但标头则不可访问 当我尝试访问 etag 标头时 浏览器引发异常 拒绝获取不安全标头 etag Chrome Safari 和 Firefox 的行为都是相同的 我
  • 来自networkx的g.nodes()不能与random.choice()一起使用

    我正在尝试在随机节点之间生成随机边 但是代码行ab choice G nodes 正在产生错误 import networkx as nx import matplotlib pyplot as plt from random import
  • 如何将 PHP 变量传递给 Angular js?

    我有一个页面 我在 Angular JS 中进行逆向工程 所有内容都通过 PHP 脚本进行回显 我知道这是不好的做法 但我正在慢慢使用 Angular 只是想将其用于 onBlur 事件 我试图弄清楚如何将变量从 PHP 传递到 Angul
  • 简化 Prolog 中的表达式

    我想问一下如何简化表达式 例如 1 2 a 5 0 b c 0 3 a 5 特别是如何在列表中分离这些表达式 这在 Prolog 中实际上很有趣 因为你不需要做任何太神奇的事情就可以让它工作 X Y 1 2 a 5 0 b c 0 X 1
  • 如何在C语言中解读一个单词并在txt文件中找到它的所有匹配项?

    因此 给定一个最多 7 个字母的字符串 我需要找到该字符串的每个排列 包含和不包含所有字母 然后检查是否可以在我的dictionary txt 文件中找到这些排列中的任何一个 并打印那些排列匹配 所以基本上 如果用户输入 try 排列将是
  • Objective-C 单例对象和全局变量

    我知道有关此主题的其他帖子 但我实际上只是从新手的阶梯上爬升的一个人 所以需要更多帮助 我的 iPhone 应用程序有几个全局变量 其中一些变量是我在类中声明并给定值的 但其他变量需要在登录过程中设置 例如令牌 然后需要可以从任何类访问应用
  • 如何通过 .NET 代码压缩和修复 ACCESS 2007 数据库?

    我需要压缩并修复 Access 2007 accdb 数据库文件 我知道 JRO JetEngine 可以使用 mdb 文件执行此操作 但我需要通过代码修复较新版本的 2007 格式 有什么建议么 EDIT 事情是这样的 我发现我可以使用
  • 有没有办法找到带有标准库的应用程序的路径?

    我想知道是否可以在 Windows 7 下找到具有标准 python 2 7 库的应用程序 例如 MS Excel 的安装目录 我的意思是 它不应该使用任何 pywin32 或 xlrd 等 也许它会查找注册表来找到安装路径 这可能非常棘手
  • 删除图像中的所有水平线和垂直线

    我想删除所有水平和垂直线 但一些小的垂直线没有被删除 添加输入和输出图像以及下面的代码 string ImageUrl C Users Jayant Desktop test images rtaImage tiff Image
  • Next.js defaultLocale 创建到同一页面的两条路由(带前缀和不带前缀)

    根据子路径路由文档对于 Next js i8n 默认区域设置没有前缀 但我的问题是 将其添加到我的 next config js 时 i18n locales en defaultLocale en localeDetection fals
  • 从 LambdaMetafactory 创建 BiConsumer

    我试图通过 LambdaMetafactory 动态创建 BiConsumer 类型的方法引用 我试图应用在https www cuba platform com blog think twice before using reflecti
  • HTTPS 连接 Python

    我正在尝试验证该目标是否公开了 https Web 服务 我有通过 HTTP 连接的代码 但我不确定如何通过 HTTPS 连接 我读过您使用 SSL 但我也读到它不支持证书错误 我得到的代码来自 python 文档 import httpl
  • 无需 jQuery 即可更改元素文本?

    我试图在不使用 jQuery 的情况下更改 div 的内容 我想通过 id 或 class 选择 div 我已经设法让附加工作 function appendHtml targetC htmldata var theDiv document
  • 使用 FFMPEG 从 FPS 不正确的视频和包含每帧时间戳的文件创建具有正确 FPS 的视频

    我有一个从网络摄像头捕获并在 python 中使用 OpenCV 的视频文件 网络摄像头标称 FPS 为 30 FPS 但由于环境的原因 实际 FPS 有所不同 有时可能低至 24 FPS 录制的视频是使用 OpenCV 创建的VideoW