You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派5上FFmpeg+SDL2解码H.264视频的性能优化问询

树莓派5 H.264 1080p解码SDL2输出优化问题

我在树莓派5上用Python脚本实现H.264 1080p视频解码并通过SDL2输出,用VLC播放同规格视频时CPU负载仅约10%,但自己用FFmpeg解码+SDL2输出的方案,单视频CPU负载高达70%。因为需要无缝切换两个视频,得同时解码两个,当前负载完全无法接受。想知道怎么优化代码,以及VLC为何这么高效?

当前Python脚本

import numpy as np
import ffmpeg  # ffmpeg-python
import sdl2.ext

in_file = ffmpeg.input('bbb1080_x264.mp4', re=None)

width = 1920
height = 1080

process1 = (
    in_file
    .output('pipe:', format='rawvideo', pix_fmt='bgra')
    .run_async(pipe_stdout=True)
)

sdl2.ext.init()
window = sdl2.ext.Window("Hello World!", size=(width, height))
window.show()
windowsurface = sdl2.SDL_GetWindowSurface(window.window)
windowArray = sdl2.ext.pixels3d(windowsurface.contents)

sdl2.ext.mouse.hide_cursor()

while True:
    in_bytes = process1.stdout.read(width * height * 4)

    if not in_bytes:
        break

    in_frame = (
        np
        .frombuffer(in_bytes, np.uint8)
        .reshape([height, width, 4])
        .transpose(1, 0, 2)
    )

    for event in sdl2.ext.get_events():
        if event.type == sdl2.SDL_QUIT:
            exit()

    windowArray[:] = in_frame
    window.refresh()

process1.wait()

VLC启动终端输出

[00007fff78c1a550] avcodec decoder error: cannot start codec (h264_v4l2m2m)
Fontconfig warning: ignoring UTF-8: not a valid region tag
[00007fff68002d70] gles2 generic error: parent window not available
[00007fff68002d70] xcb generic error: window not available
[00007fff680013f0] mmal_xsplitter vout display: Try drm
[00007fff68002d70] drm_vout generic: <<<< OpenDrmVout: Fmt=I420
[00007fff68002d70] drm_vout generic error: Failed to get xlease`

注:从输出看VLC并未使用h264_v4l2m2m硬件加速。


一、VLC为何如此高效?

虽然VLC的h264_v4l2m2m解码器启动失败,但它自动 fallback 到了MMAL硬件解码(树莓派原生多媒体加速框架),同时采用了DRM直接渲染:

  1. MMAL硬件解码:把H.264解码任务完全交给树莓派的VideoCore GPU,几乎不占用CPU资源。
  2. DRM直接渲染:解码后的视频帧直接通过DRM(Direct Rendering Manager)输出到显示设备,跳过CPU参与的像素格式转换和内存拷贝步骤,大幅降低CPU负载。
  3. 流水线并行处理:VLC内部实现了解码、渲染的并行流水线,解码下一帧的同时渲染当前帧,减少CPU闲置等待时间。

二、代码优化方案

1. 启用FFmpeg硬件解码(v4l2m2m)

强制FFmpeg使用树莓派的v4l2m2m硬件解码器,避免CPU软解码。修改FFmpeg命令部分,指定解码器并输出硬件原生像素格式(如YUV420P),减少格式转换开销:

process1 = (
    in_file
    .video.decoder('h264_v4l2m2m')  # 启用硬件解码
    .output('pipe:', format='rawvideo', pix_fmt='yuv420p')  # 输出原生YUV格式
    .run_async(pipe_stdout=True)
)

2. 使用SDL2硬件加速渲染(SDL_Renderer)

放弃SDL_GetWindowSurface的软件渲染方式,改用SDL_Renderer和纹理(SDL_Texture)进行硬件渲染,避免CPU逐像素拷贝:

  • 创建渲染器时指定SDL_RENDERER_ACCELERATED标志启用硬件加速
  • 直接将解码后的YUV帧上传到纹理,由GPU负责格式转换和渲染

3. 移除不必要的CPU操作

去掉numpy的transpose和reshape操作,让FFmpeg输出正确的宽高顺序,或让SDL渲染时处理方向问题,减少CPU计算开销。

4. 优化双视频解码的并行处理

用Pythonmultiprocessing模块分别启动两个硬件解码进程,每个进程独立处理一个视频的解码(或解码后将帧传递给主进程渲染),确保两个任务都能利用硬件加速,避免CPU串行处理导致负载过高。

修改后的示例代码(核心优化点)

import ffmpeg
import sdl2
import sdl2.ext

def init_sdl(width, height):
    sdl2.ext.init()
    window = sdl2.ext.Window("Hardware Accel Playback", size=(width, height))
    window.show()
    # 创建硬件加速渲染器,开启垂直同步
    renderer = sdl2.ext.Renderer(window, flags=sdl2.SDL_RENDERER_ACCELERATED | sdl2.SDL_RENDERER_PRESENTVSYNC)
    # 创建YUV420P格式的纹理
    texture = renderer.create_texture(sdl2.SDL_PIXELFORMAT_IYUV, sdl2.SDL_TEXTUREACCESS_STREAMING, width, height)
    return window, renderer, texture

def decode_video(path, width, height):
    return (
        ffmpeg.input(path, re=None)
        .video.decoder('h264_v4l2m2m')
        .output('pipe:', format='rawvideo', pix_fmt='yuv420p')
        .run_async(pipe_stdout=True, pipe_stderr=True)
    )

def main():
    width, height = 1920, 1080
    window, renderer, texture = init_sdl(width, height)
    process = decode_video('bbb1080_x264.mp4', width, height)
    
    frame_size = width * height * 3 // 2  # YUV420P的帧大小
    running = True
    
    while running:
        # 读取一帧YUV数据
        frame_data = process.stdout.read(frame_size)
        if not frame_data:
            break
        
        # 更新纹理数据
        texture.update(None, frame_data, width)
        
        # 处理退出事件
        for event in sdl2.ext.get_events():
            if event.type == sdl2.SDL_QUIT:
                running = False
                break
        
        # 渲染并显示
        renderer.clear()
        renderer.copy(texture)
        renderer.present()
    
    process.wait()
    sdl2.ext.quit()

if __name__ == "__main__":
    main()

内容的提问来源于stack exchange,提问作者aforino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 04:10:57