【问题标题】:How extract JPEG image from H264 stream in constant time如何在恒定时间内从 H264 流中提取 JPEG 图像
【发布时间】:2021-10-26 18:01:25
【问题描述】:

我想从磁盘上的 H264 流中提取 JPEG 帧。提取需要尽可能快以满足我的实时要求。

到目前为止,我一直在使用ffmpeg-python lib,它只是ffmpeg 的python 包装器。这是一个代码sn-p:

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。

【问题讨论】:

  • 暂时忘记包装器,直到您首先获得普通的 ffmpeg 工作。显示正在执行的实际 ffmpeg 命令,并复制并粘贴 ffmpeg 日志中的所有文本。
  • 好的,现在包括这些。
  • 当输入视频是原始 h264 流时,我认为您无法获得恒定的时间。这是因为原始 h264 流是不可搜索(例如:您不能使用 ffmpeg -ss 00:00:03 -i 5sec.h264... 搜索到第三秒并从那里开始提取帧)。因此,FFmpeg 会读取从文件开头到所需帧的所有帧。使用像5sec.mp4 这样的容器我可以解决它。但是快速搜索只针对一个关键帧,所以你这取决于输入的“GOP大小”。
  • 我使用以下命令将 5sec 转换为 .mp4:ffmpeg -framerate 25 -i 5sec.h264 -c copy 5sec.mp4 在 mp4 文件上使用我原来的 ffmpeg 命令会产生相同的查找时间到第 125 帧:ffmpeg -i 5sec.mp4 -frames:v 1 -filter:v "select=gte(n\,25)" -f image2 frame2.jpg。还是很慢!

标签: python ffmpeg video-streaming video-processing h.264


【解决方案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过滤器测量时间,并使用搜索:

示例提取帧1440而不是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


在这两种情况下frame.jpg 都显示1440:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 2021-06-26
    • 1970-01-01
    相关资源
    最近更新 更多