树莓派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直接渲染:
- MMAL硬件解码:把H.264解码任务完全交给树莓派的VideoCore GPU,几乎不占用CPU资源。
- DRM直接渲染:解码后的视频帧直接通过DRM(Direct Rendering Manager)输出到显示设备,跳过CPU参与的像素格式转换和内存拷贝步骤,大幅降低CPU负载。
- 流水线并行处理: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
相关产品推荐
相关产品推荐

