【问题标题】:ffmpeg pipe images to redisffmpeg 管道图像到 redis
【发布时间】:2021-01-04 01:06:19
【问题描述】:

我需要找到一种方法将 ffmpeg 帧保存到 redis 变量中。

目前我正在将 png 图像从 ffmpeg 传送到 redis,但它连接了所有图像:

ffmpeg -re -i udp://127.0.0.1:5545 -c:v png -r 1 -update 1 - | redis-cli -x HSET sb4up

是否可以每秒用一个新帧更新 redis 变量?

【问题讨论】:

  • 可能有一种方法无需编写太多 "glue" 代码即可将各个部分连接在一起,但如果需要一些代码,哪种语言可以接受?您的视频流是否包含透明度或选择 PNG 格式有什么原因?
  • python,bash 脚本,就可以了。 png没有具体原因,也可能是jpeg。看到ffmpeg支持mq输出,说不定还有一种redis插件……谢谢。

标签: ffmpeg redis


【解决方案1】:

我一直在思考这个问题。我不是ffmpeg 的专家,可能有更简单的方法,所以如果有人让我知道更好的方法,我可能会删除它。

目前,我的想法是,如果ffmpeg 将 JPEG 或 PNG 作为输出写入,您需要一个能够解析连接流并搜索 START-OF-IMAGE/END-OF-IMAGE 或 ( JPEG SOI/EOI 或 PNG IHDR/IEND)标记并理解块和部分。所以,我认为强制ffmpeg 写入固定长度的图像可能更简单,然后你可以只读取N 字节,知道它对应于一个帧并且你不需要一堆 "glue" 编写和维护的代码 - 只是标准工具。

我没有你的 UDP 流,所以我只使用了 .mov 文件。然后我强制RGB24 编码和一个固定大小、调整大小的 640x480 帧。然后我将其拆分为 640x480x3 RGB 字节的块,并要求 ImageMagick 从原始视频创建一个 PNG 并将其作为 PNG 输入 Redis:

#!/bin/bash

# Set height and width of frame, and size in bytes
h=480
w=640
bytes=$((h*w*3))

ffmpeg -i video.mov -r 1 -s ${w}x${h} -update 1 -pix_fmt rgb24 -f rawvideo - 2> /dev/null | \
   gsplit -b $bytes --filter="magick -depth 8 -size ${w}x${h} rgb:- png:- | redis-cli -x SET sb4up"

然后我使用 OpenCV 每 0.5 秒从 Redis 抓取图像并显示结果。

#!/usr/bin/env python3

import redis
import cv2
import numpy as np

host='localhost'
port=6379
        
r = redis.Redis(host,port)

while True:
   png=r.get('sb4up')
   frame = cv2.imdecode(np.frombuffer(png,np.uint8), cv2.IMREAD_COLOR)
   cv2.imshow("Viewer", frame)
   cv2.waitKey(500)

注意:我使用 gsplit 表示 GNU split,因为我在 Mac 上。在 Linux 上,只需使用 split

注意:显然,您可以使用不同大小的框架,只要您在代码的两个部分都更新它 - 即在 gsplitImageMagick 中。

注意:您不一定需要安装 ImageMagick 并对每个图像进行 PNG 编码,因为在 Redis 所以空间不会成为问题,因为没有数千帧,所以您可以省略 ImageMagick 并将原始 RGB24 数据写入 Redis 而无需ImageMagick。然后从 Redis 中获取原始数据并使用np.reshape() 将其制成正确形状的图像。

我仍在思考,可能会想出更好的东西... Perl 可能适合在模式上拆分二进制流...

另一个想法是让ffmpegframe-%03d.png 之类的序列号写入PNG 文件,然后运行fswatch/inotify 来抓取各个帧并将它们填充到Redis中。

【讨论】:

    【解决方案2】:

    不知为何。我有编写一些 Python 来拆分连续的连接 PNG 文件流并在每个文件到达时将其发送到 Redis 的冲动。

    程序读取PNG chunks 中的PNG 文件流,而不是1MB 块并查找IEND 标记,如果您的单个PNG 图像的大小为33kB,则会导致30 图像延迟。每个 PNG 块都有一个 “块大小”(网络字节顺序为 4 个字节)和一个 “块类型”(4 个字节)。第一个块是 8 字节签名,图像中的最后一个块是 IEND 块。当块到达时,它们被读取并收集在一个名为 wholePNGbytearray 中。当 PNG 结束时,我将其发送到 Redis

    #!/usr/bin/env python3
    ################################################################################
    # pngsplit - split a concatenated stream of PNG files
    # Mark Setchell
    ################################################################################
    
    import redis
    import os, sys
    from struct import unpack
    
    # Ensure stdin is binary
    fp = os.fdopen(sys.stdin.fileno(), 'rb')
    
    # Connect to Redis
    host='localhost'
    port=6379
    r = redis.Redis(host,port)
    
    # Define a valid PNG signature
    validSig = bytes.fromhex('89504e470d0a1a0a')
    expectingSig = True
    
    while True:
       # We expect an 8-byte PNG signature at the start of each PNG file
       if expectingSig:
          # Get signature and ensure it is a PNG signature
          rawSig = fp.read(8)
          if len(rawSig) != 8:
             break
          if rawSig != validSig:
             print('ERROR: Not a valid PNG signature', file=sys.stderr)
             sys.exit(1)
          expectingSig = False
          # Start building a new PNG to send to Redis
          wholePNG = bytearray(validSig)
    
       # Read 4 bytes and extract PNG chunk length
       rawChunkLength = fp.read(4)
       chunkLen  = unpack('!I', rawChunkLength)[0]
       wholePNG += rawChunkLength
    
       # Read 4 bytes and extract PNG chunk type
       rawChunkType = fp.read(4)
       b0,b1,b2,b3 = unpack('BBBB', rawChunkType)
       chunkType = chr(b0) + chr(b1) + chr(b2) + chr(b3)
       print(f'DEBUG: {chunkType} PNG chunk, length={chunkLen}')
       wholePNG += rawChunkType
    
       # Read the chunk data
       if chunkLen > 0:
          rawData = fp.read(chunkLen)
          wholePNG += rawData
    
       # Read the CRC
       rawCRC = fp.read(4)
       wholePNG += rawCRC
    
       # If IEND chunk
       if chunkType == 'IEND':
          print('DEBUG: Received IEND chunk - sending to Redis')
          r.set('sb4up', bytes(wholePNG))
          # I guess we are now expecting a new PNG
          expectingSig = True
    

    你可以这样运行它:

    ffmpeg command producing PNG stream | ./pngsplit.py
    

    它会在每个 PNG 到达时将其作为 sb4up 发送到 Redis。

    我在一个加载了随机 PNG 图像的目录中进行了这样的测试:

    while : ; do cat *.png; done | ./pngsplit.py
    

    关键字:Python、图像处理、ffmpeg、视频、流、拆分、PNG 块、连接。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-25
      • 2017-09-24
      • 2019-05-17
      • 2018-03-27
      • 2011-08-15
      • 2013-07-14
      • 2016-03-14
      • 2020-05-08
      相关资源
      最近更新 更多