亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

解碼 h264 時的 ffmpeg 延遲

解碼 h264 時的 ffmpeg 延遲

慕森卡 2022-08-16 15:35:07
我正在獲取原始RGB幀,將它們編碼為h264,然后將其解碼回原始RGB幀。[RGB frame] ------ encoder ------> [h264 stream] ------ decoder ------> [RGB frame]              ^               ^                    ^               ^        encoder_write    encoder_read        decoder_write    decoder_read我想盡快檢索解碼的幀。但是,無論等待多長時間,似乎總會有一幀延遲。1 在此示例中,我每 2 秒向編碼器提供一幀:$ python demo.py 2>/dev/nulltime=0 frames=1 encoder_writetime=2 frames=2 encoder_writetime=2 frames=1 decoder_read   <-- decoded output is delayed by extra frametime=4 frames=3 encoder_writetime=4 frames=2 decoder_readtime=6 frames=4 encoder_writetime=6 frames=3 decoder_read...我想要的:$ python demo.py 2>/dev/nulltime=0 frames=1 encoder_writetime=0 frames=1 decoder_read   <-- decode immediately after encodetime=2 frames=2 encoder_writetime=2 frames=2 decoder_readtime=4 frames=3 encoder_writetime=4 frames=3 decoder_readtime=6 frames=4 encoder_writetime=6 frames=4 decoder_read...編碼器和解碼器 ffmpeg 進程使用以下參數運行:encoder: ffmpeg -f rawvideo -pix_fmt rgb24 -s 224x224 -i pipe: \                -f h264 -tune zerolatency pipe:decoder: ffmpeg -probesize 32 -flags low_delay \                -f h264 -i pipe: \                -f rawvideo -pix_fmt rgb24 -s 224x224 pipe:下面是完整的可重現示例。無需外部視頻文件。只需復制,粘貼并運行!python demo.py 2>/dev/null1 我做了一些測試,似乎解碼器在解碼當前幀之前正在等待下一幀的NAL標頭(十六進制)。人們會希望前綴就足夠了,但它也會等待接下來的兩個字節!00 00 00 01 41 8800 00 00 01
查看完整描述

1 回答

?
慕姐4208626

TA貢獻1852條經驗 獲得超7個贊

添加到解碼器參數中。-probesize 32

  • 將解碼器命令設置為:

    cmd = "ffmpeg -probesize 32 -f h264 -i pipe: -f rawvideo -pix_fmt rgb24 -s 224x224 pipe:"

我在這里找到了解決方案:如何使用FFmpeg最大限度地減少實時流中的延遲。

根據FFmpeg StreamingGuide

此外,將 -probesize 和 -analyzeduration 設置為低值可能有助于您的流更快地啟動。

添加參數后,我得到了9行...而不是大約120行。-probesize 32Decoder written 862 bytes


更新:

我找不到解決方案,但我設法形成了一個簡單的問題演示。

代碼示例不使用兩個子進程和 4 個線程,而是使用一個子進程,而不使用 Python 線程。

該示例使用以下“篩選器圖”:

http://img1.sycdn.imooc.com//62fb48f800017ac104670116.jpg

請參見: 流復制章節

我發現,要將第一幀從輸入“推送”到輸出,我們需要從第二幀的開頭至少寫入額外的字節。4112

下面是代碼示例:

import cv2

import numpy as np

import subprocess as sp


width, height, n_frames, fps = 256, 256, 10, 1  # 10 frames, resolution 256x256, and 1 fps



def make_bmp_frame_as_bytes(i):

    """ Build synthetic image for testing, encode as BMP and convert to bytes sequence """

    p = width//50

    img = np.full((height, width, 3), 60, np.uint8)

    cv2.putText(img, str(i+1), (width//2-p*10*len(str(i+1)), height//2+p*10), cv2.FONT_HERSHEY_DUPLEX, p, (255, 30, 30), p*2)  # Blue number


    # BMP Encode img into bmp_img

    _, bmp_img = cv2.imencode(".BMP", img)

    bmp_img_bytes = bmp_img.tobytes()


    return bmp_img_bytes




# BMP in, BMP out:

process = sp.Popen(f'ffmpeg -debug_ts -probesize 32 -f bmp_pipe -framerate {fps} -an -sn -dn -i pipe: -f image2pipe -codec copy -an -sn -dn pipe:', stdin=sp.PIPE, stdout=sp.PIPE)


# Build image (number -1) before the loop.

bmp_img_bytes = make_bmp_frame_as_bytes(-1)


# Write one BMP encoded image before the loop.

process.stdin.write(bmp_img_bytes)

process.stdin.flush()


for i in range(n_frames):

    # Build image (number i) before the loop.

    bmp_img_bytes = make_bmp_frame_as_bytes(i)


    # Write 4112 first bytes of the BMP encoded image.

    # Writing 4112 "push" forward the previous image (writing less than 4112 bytes hals on the first frame).

    process.stdin.write(bmp_img_bytes[0:4112])

    process.stdin.flush()


    # Read output BMP encoded image from stdout PIPE.

    buffer = process.stdout.read(width*height*3 + 54)   # BMP header is 54 bytes

    buffer = np.frombuffer(buffer, np.uint8)

    frame = cv2.imdecode(buffer, cv2.IMREAD_COLOR)  # Decode BMP image (using OpenCV).


    # Display the image

    cv2.imshow('frame', frame)

    cv2.waitKey(1000)


    # Write the next bytes of the BMP encoded image (from byte 4112 to the end).

    process.stdin.write(bmp_img_bytes[4112:])

    process.stdin.flush()



process.stdin.close()

buffer = process.stdout.read(width*height*3 + 54)   # Read last image

process.stdout.close()


# Wait for sub-process to finish

process.wait()


cv2.destroyAllWindows()

  • 我不知道為什么是字節。
    我使用的是4.2.2版本,在Windows 10下靜態鏈接()。
    我沒有檢查其他版本/平臺的字節是否持久。4112FFmpegffmpeg.exe4112

  • 我懷疑“延遲問題”是Demuxers固有的。FFmpeg

  • 我找不到任何參數/標志來防止這個問題。

  • rawvideo demuxer是唯一一個沒有增加延遲的解復用器(我發現)。

我希望更簡單的示例代碼有助于找到延遲問題的解決方案...


更新:

H.264 流示例:

該示例使用以下“篩選器圖”:

http://img1.sycdn.imooc.com//62fb490d0001399805060111.jpg

代碼示例在寫入每個編碼幀后寫入 AUD NAL 單元。


AUD(訪問單元分隔符)是可選的 NAL 單元,位于編碼幀的開頭。

顯然,在寫入編碼幀后寫入AUD會將編碼幀從解復用器“推送”到解碼器。


下面是一個代碼示例:


import cv2

import numpy as np

import subprocess as sp

import json


width, height, n_frames, fps = 256, 256, 100, 1  # 100 frames, resolution 256x256, and 1 fps



def make_raw_frame_as_bytes(i):

    """ Build synthetic "raw BGR" image for testing, convert the image to bytes sequence """

    p = width//60

    img = np.full((height, width, 3), 60, np.uint8)

    cv2.putText(img, str(i+1), (width//2-p*10*len(str(i+1)), height//2+p*10), cv2.FONT_HERSHEY_DUPLEX, p, (255, 30, 30), p*2)  # Blue number


    raw_img_bytes = img.tobytes()


    return raw_img_bytes



# Build input file input.264 (AVC encoded elementary stream)

################################################################################

process = sp.Popen(f'ffmpeg -y -video_size {width}x{height} -pixel_format bgr24 -f rawvideo -r {fps} -an -sn -dn -i pipe: -f h264 -g 1 -pix_fmt yuv444p -crf 10 -tune zerolatency -an -sn -dn input.264', stdin=sp.PIPE)


#-x264-params aud=1

#Adds [  0,   0,   0,   1,   9,  16 ] to the beginning of each encoded frame

aud_bytes = b'\x00\x00\x00\x01\t\x10'  #Access Unit Delimiter

#process = sp.Popen(f'ffmpeg -y -video_size {width}x{height} -pixel_format bgr24 -f rawvideo -r {fps} -an -sn -dn -i pipe: -f h264 -g 1 -pix_fmt yuv444p -crf 10 -tune zerolatency -x264-params aud=1 -an -sn -dn input.264', stdin=sp.PIPE)


for i in range(n_frames):

    raw_img_bytes = make_raw_frame_as_bytes(i)

    process.stdin.write(raw_img_bytes) # Write raw video frame to input stream of ffmpeg sub-process.


process.stdin.close()

process.wait()

################################################################################


# Execute FFprobe and create JSON file (showing pkt_pos and pkt_size for every encoded frame):

sp.run('ffprobe -print_format json -show_frames input.264', stdout=open('input_probe.json', 'w'))


# Read FFprobe output to dictionary p

with open('input_probe.json') as f:

    p = json.load(f)['frames']



# Input PIPE: H.264 encoded video, output PIPE: decoded video frames in raw BGR video format

process = sp.Popen(f'ffmpeg -probesize 32 -flags low_delay -f h264 -framerate {fps} -an -sn -dn -i pipe: -f rawvideo -s {width}x{height} -pix_fmt bgr24 -an -sn -dn pipe:', stdin=sp.PIPE, stdout=sp.PIPE)


f = open('input.264', 'rb')


process.stdin.write(aud_bytes)  # Write AUD NAL unit before the first encoded frame.


for i in range(n_frames-1):

    # Read H.264 encoded video frame

    h264_frame_bytes = f.read(int(p[i]['pkt_size']))


    process.stdin.write(h264_frame_bytes)

    process.stdin.write(aud_bytes)  # Write AUD NAL unit after the encoded frame.

    process.stdin.flush()


    # Read decoded video frame (in raw video format) from stdout PIPE.

    buffer = process.stdout.read(width*height*3)

    frame = np.frombuffer(buffer, np.uint8).reshape(height, width, 3)


    # Display the decoded video frame

    cv2.imshow('frame', frame)

    cv2.waitKey(1)


# Write last encoded frame

h264_frame_bytes = f.read(int(p[n_frames-1]['pkt_size']))

process.stdin.write(h264_frame_bytes)


f.close()



process.stdin.close()

buffer = process.stdout.read(width*height*3)   # Read the last video frame

process.stdout.close()


# Wait for sub-process to finish

process.wait()

更新:

額外幀延遲的原因是 h264 基本流沒有“幀結束”信號,并且 NAL 單元標頭中沒有“有效負載大小”字段。

檢測幀何時結束的唯一方法是查看下一幀的開始位置。

請參見: 檢測 H.264 視頻流中幀的結尾。
以及如何知道H.264流中代表圖片的NAL單位的數量。

為了避免等待下一幀的開始,必須使用“傳輸流”層或視頻容器格式。
傳輸流和少量容器格式允許接收器(解復用器)進行“幀尾”檢測。

我嘗試使用MPEG-2傳輸流,但它增加了一幀的延遲。
[我沒有嘗試RTSP協議,因為它不適用于管道]。

使用 Flash 視頻 (FLV) 容器可將延遲降低到單個幀。
FLV 容器在數據包標頭中有一個“有效負載大小”字段,允許解復用器避免等待下一幀。

用于使用 FLV 容器和 H.264 編解碼器的命令:

cmd = (

    "ffmpeg "

    "-f rawvideo -pix_fmt rgb24 -s 224x224 "

    "-i pipe: "

    "-vcodec libx264 "

    "-f flv "

    "-tune zerolatency "

    "pipe:"

)

encoder_process = subprocess.Popen(

    cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE

)


cmd = (

    "ffmpeg "

    "-probesize 32 "

    "-flags low_delay "

    "-f flv "

    "-vcodec h264 "

    "-i pipe: "

    "-f rawvideo -pix_fmt rgb24 -s 224x224 "

    "pipe:"

)


decoder_process = subprocess.Popen(

    cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE

)

在上面的命令中,FFmpeg 將 FLV 多路復用器用于編碼器進程,將 FLV 解復用器用于解碼器進程。


輸出結果:


time=0 frames=1   encoder_write

time=0 frames=1   decoder_read  psnr=49.0

time=2 frames=2   encoder_write

time=2 frames=2   decoder_read  psnr=48.3

time=4 frames=3   encoder_write

time=4 frames=3   decoder_read  psnr=45.8

time=6 frames=4   encoder_write

time=6 frames=4   decoder_read  psnr=46.7

如您所見,沒有額外的幀延遲。


其他也有效的容器是:AVI和MKV。


查看完整回答
反對 回復 2022-08-16
  • 1 回答
  • 0 關注
  • 532 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號