簡體   English   中英

如何使用 pyav 或 opencv 對原始 H.264 數據的實時 stream 進行解碼?

[英]How to use pyav or opencv to decode a live stream of raw H.264 data?

數據是通過socket接收的,沒有更多的shell,它們是純IPB幀,以NAL Header開頭(類似於00 00 00 01)。 我現在正在使用 pyav 解碼幀,但我只能在接收到第二個 pps 信息(在關鍵幀中)之后解碼數據(所以我發送到我的解碼線程的數據塊可以以 pps 和 sps 開頭),否則decode() 或 demux() 將返回錯誤“non-existing PPS 0 referenced decode_slice_header error”。

我想將數據饋送到可以記住前一個 P 幀的持續解碼器,因此在饋送一個 B 幀后,解碼器返回一個解碼的視頻幀。 或者某種形式的 IO 可以作為容器打開並通過另一個線程繼續向其中寫入數據。

這是我的關鍵代碼:

#read thread... read until get a key frame, then make a new io.BytesIO() to store the new data.
rawFrames = io.BytesIO()
while flag_get_keyFrame:()
    ....
    content= socket.recv(2048)
    rawFrames.write(content)
    ....

#decode thread... decode content between two key frames
....
rawFrames.seek(0)
container = av.open(rawFrames)
for packet in container.demux():
    for frame in packet.decode():
        self.frames.append(frame)
....

我的代碼將播放視頻,但有 3~4 秒的延遲。 所以我不會把所有的東西都放在這里,因為我知道它實際上並沒有達到我想要實現的目標。 我想在收到第一個關鍵幀后播放視頻,並在收到后立即解碼以下幀。 Pyav opencv ffmpeg 或其他東西,我怎樣才能實現我的目標?

它通常會延遲 3~4 秒,因為您正在讀取編碼數據並通過 CPU 對其進行解碼需要時間。

  1. 如果您有 GPU 硬件,您可以使用 FFMPEG 通過 GPU 解碼 H264。 是一個例子。
  2. 如果您沒有 GPU,在 CPU 上解碼 H264 總是會導致延遲。 您可以使用 FFMPEG 進行有效解碼,但這也會將總延遲降低近 10%

經過數小時找到這個問題的答案。 我自己想通了。

對於單線程,您可以執行以下操作:

rawData = io.BytesIO()
container = av.open(rawData, format="h264", mode='r')
cur_pos = 0
while True:
    data = await websocket.recv()
    rawData.write(data)
    rawData.seek(cur_pos)
    for packet in container.demux():
        if packet.size == 0:
            continue
        cur_pos += packet.size
        for frame in packet.decode():
            self.frames.append(frame)

這就是基本思想。 我已經制定了一個將接收線程和解碼線程分開的通用版本。 如果 CPU 跟不上解碼速度,代碼也會跳幀,並從下一個關鍵幀開始解碼(這樣就不會出現撕裂的綠屏效果)。 這是代碼的完整版本:

import asyncio
import av
import cv2
import io
from multiprocessing import Process, Queue, Event
import time
import websockets

def display_frame(frame, start_time, pts_offset, frame_rate):
    if frame.pts is not None:
        play_time = (frame.pts - pts_offset) * frame.time_base.numerator / frame.time_base.denominator
        if start_time is not None:
            current_time = time.time() - start_time
            time_diff = play_time - current_time
            if time_diff > 1 / frame_rate:
                return False
            if time_diff > 0:
                time.sleep(time_diff)
    img = frame.to_ndarray(format='bgr24')
    cv2.imshow('Video', img)
    return True

def get_pts(frame):
    return frame.pts

def render(terminated, data_queue):
    rawData = io.BytesIO()
    cur_pos = 0
    frames_buffer = []
    start_time = None
    pts_offset = None
    got_key_frame = False
    while not terminated.is_set():
        try:
            data = data_queue.get_nowait()
        except:
            time.sleep(0.01)
            continue
        rawData.write(data)
        rawData.seek(cur_pos)
        if cur_pos == 0:
            container = av.open(rawData, mode='r')
            original_codec_ctx = container.streams.video[0].codec_context
            codec = av.codec.CodecContext.create(original_codec_ctx.name, 'r')
        cur_pos += len(data)
        dts = None
        for packet in container.demux():
            if packet.size == 0:
                continue
            dts = packet.dts
            if pts_offset is None:
                pts_offset = packet.pts
            if not got_key_frame and packet.is_keyframe:
                got_key_frame = True
            if data_queue.qsize() > 8 and not packet.is_keyframe:
                got_key_frame = False
                continue
            if not got_key_frame:
                continue
            frames = codec.decode(packet)
            if start_time is None:
                start_time = time.time()
            frames_buffer += frames
            frames_buffer.sort(key=get_pts)
            for frame in frames_buffer:
                if display_frame(frame, start_time, pts_offset, codec.framerate):
                    frames_buffer.remove(frame)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
        if dts is not None:
            container.seek(25000)
        rawData.seek(cur_pos)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    terminated.set()
    cv2.destroyAllWindows()

async def receive_encoded_video(websocket, path):
    data_queue = Queue()
    terminated = Event()
    p = Process(
        target=render,
        args=(terminated, data_queue)
    )
    p.start()
    while not terminated.is_set():
        try:
            data = await websocket.recv()
        except:
            break
        data_queue.put(data)
    terminated.set()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM