python - 如何在恒定时间内从 H264 流中提取 JPEG 图像
问题描述
我想从磁盘上的 H264 流中提取 JPEG 帧。提取需要尽可能快地满足我的实时要求。
到目前为止,我一直在使用ffmpeg-python
lib,它只是ffmpeg
. 这是一个代码片段:
out, _ = (
ffmpeg
.input('./5sec.h264')
.filter('select', 'gte(n,{})'.format(144))
.output('pipe:', vframes=1, format='image2', vcodec='h264')
.run(capture_stdout=True)
)
这会将 jpeg 输出到标准输出,通过一些努力,我可以将其读入我的程序中。
但是,随着我使用越来越大的流文件,获取 JPEG 的提取时间会增加。我认为查找时间会保持不变,因为ffmpeg
高度优化?
是否有固定时间的解决方案来从磁盘上的 h264(甚至 mjpeg)格式流中查找和返回帧?
编辑:继承人我在没有 python 包装器的情况下使用的命令:
ffmpeg -i 5sec.h264 -frames:v 1 -filter:v "select=gte(n\,25)" -f image2 frame.jpg
这是输出:
ffmpeg version 4.1.6-1~deb10u1+rpt2 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 8 (Raspbian 8.3.0-6+rpi1)
configuration: --prefix=/usr --extra-version='1~deb10u1+rpt2' --toolchain=hardened --incdir=/usr/include/arm-linux-gnueabihf --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-omx-rpi --enable-mmal --enable-neon --enable-rpi --enable-vout-drm --enable-v4l2-request --enable-libudev --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared --libdir=/usr/lib/arm-linux-gnueabihf --cpu=arm1176jzf-s --arch=arm
libavutil 56. 22.100 / 56. 22.100
libavcodec 58. 35.100 / 58. 35.100
libavformat 58. 20.100 / 58. 20.100
libavdevice 58. 5.100 / 58. 5.100
libavfilter 7. 40.101 / 7. 40.101
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 3.100 / 5. 3.100
libswresample 3. 3.100 / 3. 3.100
libpostproc 55. 3.100 / 55. 3.100
Input #0, h264, from '5sec.h264':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: h264 (High), yuv420p(progressive), 640x480, 25 fps, 25 tbr, 1200k tbn, 50 tbc
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> mjpeg (native))
Press [q] to stop, [?] for help
[swscaler @ 0x1a25390] deprecated pixel format used, make sure you did set range correctly
Output #0, image2, to 'frame.jpg':
Metadata:
encoder : Lavf58.20.100
Stream #0:0: Video: mjpeg, yuvj420p(pc), 640x480, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc
Metadata:
encoder : Lavc58.35.100 mjpeg
Side data:
cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame= 1 fps=0.4 q=6.8 Lsize=N/A time=00:00:01.04 bitrate=N/A speed=0.467x
video:63kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
请注意,实现的 FPS 为 0.4。当我将请求的帧增加到第 125 帧而不是第 25 帧时,FPS 下降到 0.1。
解决方案
假设 H.264 流有一个容器文件,您可能会寻找帧的位置,而不是从文件开头读取所有帧。
不要使用select
过滤器,而是使用-ss
参数(input
方法的参数)。
FFmpeg 命令行类似于以下内容:
ffmpeg -ss 14.4 -i in.mp4 -vframes 1 frame.jpg
为什么我们需要一个容器(比如 MP4)?
MP4 容器文件使用带有视频文件中每个关键帧位置的“索引”。
当我们需要查找文件中的特定时间时,我们可以跳到“时间”之前最近的关键帧,并解码剩余的几帧。
没有容器的原始 h264 流没有索引,因此我们无法进行查找。
首先创建一个示例 MP4 文件(用于使代码示例可重现)。
以下代码示例使用高级帧计数器构建 10Hz 的合成视频文件:
import ffmpeg
in_file_name = 'in.mp4'
fps = 10
# Build synthetic video, for testing - 1500 frames with advanced counder:
# Use g=50, for forcing a key frame every 50 frames (for testing).
(
ffmpeg
.input('testsrc=size=192x108:rate=1:duration=1500', f='lavfi', r=fps)
.filter('setpts', 'N/{}/TB'.format(fps))
.output(in_file_name, vcodec='libx264', g=50, crf=17, pix_fmt='yuv420p', loglevel='panic')
.global_args('-hide_banner')
.overwrite_output()
.run()
)
select
使用过滤器测量时间,并使用seek:
该示例提取 frame1440
而不是144
,以便更好地显示时间差异。
import ffmpeg
import time
in_file_name = 'in.mp4'
fps = 10
extracted_frame = 1440
# Build synthetic video, for testing - 1500 frames with advanced counder:
# Use g=50, for forcing a key frame every 50 frames (for testing).
#(
# ffmpeg
# .input('testsrc=size=192x108:rate=1:duration=1500', f='lavfi', r=fps)
# .filter('setpts', 'N/{}/TB'.format(fps))
# .output(in_file_name, vcodec='libx264', g=50, crf=17, pix_fmt='yuv420p', loglevel='panic')
# .global_args('-hide_banner')
# .overwrite_output()
# .run()
#)
start = time.time()
for i in range(10): # Measure 10 iterations.
# Extract the extracted_frame'th frame into frame.jpg
(
ffmpeg
.input(in_file_name)
.filter('select', 'gte(n,{})'.format(extracted_frame))
.output('frame.jpg', vframes=1, loglevel='panic')
.global_args('-hide_banner')
.overwrite_output()
.run()
)
end = time.time()
print('Time using select filter: {}'.format(end - start))
start = time.time()
for i in range(10): # Measure 10 iterations.
# Extract the extracted_frame'th frame into frame.jpg
(
ffmpeg
.input(in_file_name, ss='{}'.format(extracted_frame / fps))
.output('frame.jpg', vframes=1, loglevel='panic')
.global_args('-hide_banner')
.overwrite_output()
.run()
)
end = time.time()
print('Time using seeking: {}'.format(end - start))
结果:
Time using select filter: 1.1203510761260986
Time using seeking: 0.36171388626098633
推荐阅读
- c - 头文件中的函数如何链接到 .c 文件?
- r - 将模型摘要打印到 R 中的文件
- typescript - 在使用 Vue Composition API 和 TypeScript 时,我应该在声明作为单个文件组件的模板 ref 时使用什么类型?
- javascript - 在 AdminLTE 中使用 Node JS、Mongoose 和 Express 禁用 HEX 代码将正斜杠 (/) 转换为 /
- intel - 为什么我们在 DH 安全会话建立中需要发起者或响应者的 enclave 身份?
- reactjs - 未考虑渲染中的 if 语句
- python-3.x - UnboundLocalError:分配前引用的局部变量“playercount”
- c# - 查找正则表达式模式匹配字符串有多个条件?
- django - 为什么我收到此错误?django.db.utils.OperationalError:致命:用户“postgres”的密码验证失败
- android - 使用 GridLayoutManager 在分页库中设置跨度大小