使用PyAV实现对字节流转换为图像数据

本文主要记录一下成功使用pyav还原h264和265视频裸流字节流到图像的方法。

因为研究这个方法掉了不少头发。确实很麻烦,经过反复尝试、对比、测试、研究,再反复尝试、对比得到的结果。

经过n天的查找与尝试,分别测试opencv-python、ffmpeg-python和pyav,其实这三种都有很多不足与问题,但是没有办法,可用可选择的就这么点。

Opencv-python

其实最好用的是opencv-python也就是cv2,但是他无法传入字节流数据,只能传入摄像头索引index、url和磁盘路径。

FFmpeg-python

测试了下问题很多,兼容性很差,经常出现各种莫名其妙的bug。

PyAv

后来测试pyav,虽然pyav是套着ffmpeg,但是他具有将字节流转换成图像的方法,参考该网址

https://github.com/PyAV-Org/PyAV/blob/main/examples/basics/parse.py

虽然他具备该功能,但是实际测试中问题也是很多,研究了n天才摸索出来。

源数据

源数据是存储在磁盘中的海康摄像头裸流文件都是ps流,我需要一遍加载读取、一遍交给线程转换成图像显示在前台UI界面中。

方法1:使用av.open创建对象

经过测试,该方法可以,但是每次都需要创建新的av.open对象,并且会出现跳帧问题,也就是说当前正在播放第一帧图像,然后你二次byte_stream.write(video_bytes)后,按理说他应该播放第n帧,结果他会有解码出第1帧图像和第n帧,交叉显示。

f=open("c.h264","rb")

byte_stream = io.BytesIO()
def get_video_stream_bytes(f):
    while chunk := f.read_bytes(10240):
        yield chunk

for video_bytes in get_video_stream_bytes(f):
    byte_stream.write(video_bytes)
    byte_stream.seek(0) 
    container = av.open(byte_stream)

    for frame in container.decode(video=0):
        frame.to_image().save("/home/hi/a/%d.jpg"%i)
        i+=1
        print(i)

方法2:创建codec对象

该方法其实是我想要的,但是会出现一个问题,第一需要前期先创建对应解码器,因为视频流可能为265或264,需要做一次判断才可以。

其次最重要的是,如果出现高码流的视频,解码图像后会出现半幅绿屏。

半幅绿屏是我放弃该方法的原因。

fh = open("c.h264", "rb")
codec = av.CodecContext.create("h264", "r")
while True:
    chunk = fh.read(1 << 16)
    packets = codec.parse(chunk)

    for packet in packets:
        frames = codec.decode(packet)
        for frame in frames:
        frame.to_image().save("./tmp/%d.jpg"%i)
        print("cc")

方法3:最终方案

该方法主要参考方法1进行改变,提供一个rawData对象,不断的清空、seek到0,但是这个方法需要注意,每次读取的块大小要大一些,否则ffmpeg会报很多错误。

其实可以根据nal头进行分帧导入,但是没有必要。直接分块读取就行了,简单粗暴。

该方法测试可行!

unsuported_byte_offset=True

这个非常重要,如果不设置为True,则视频帧会交叉显示第一帧与n帧

rawData.truncate(0)

这个也非常重要,必须要清空内存空间,否则也会交叉显示第一帧与n帧


import av
f=open("c.h264",'rb')
rawData = io.BytesIO()
container=None
video=None

#每次读取的块大小,建议5M以上
block_size=1024*1024*5

while True:
  
    buffer=f.read(block_size)
    #必须要清空
    rawData.truncate(0)
    rawData.write(buffer)
    rawData.seek(0)
    
    if container is None:
        container = av.open(rawData)
        video=container.streams.video[0]
        video.thread_type='NONE'    
    else:
        #unsuported_byte_offset=True 这个非常重要,如果不设置为True,则视频帧会交叉显示第一帧与n帧
        container.seek(0,unsupported_byte_offset=True)

    for frame in container.decode(video):
        if frame.key_frame:             
            frame.to_image().save("./tmp/%d.jpg"%i)
            i+=1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值