如何用Python多进程启动FFmpeg并从主进程发送帧字符串?
老哥,你的问题我太懂了——单进程里Python的GIL把FFmpeg绑得死死的,明明有3个空闲核心却用不上,CPU拉满还导致直播卡顿,简直头疼。刚好我之前处理过类似的场景,给你一套实打实的解决方案:用Python把FFmpeg放到独立的子进程里跑,主进程专门负责生成帧,再通过管道把帧数据传给FFmpeg,彻底绕开GIL的限制,让FFmpeg能自由啃剩下的CPU核心。
核心思路
本质就是把FFmpeg从Python的GIL枷锁里解放出来:用subprocess模块启动FFmpeg作为系统级子进程(完全脱离Python解释器的控制),主进程专注于帧生成,两者通过管道做数据传输。这样FFmpeg可以随便用空闲的CPU核心,和主进程的帧处理并行执行,CPU利用率直接拉满,卡顿自然就没了。
具体实现步骤&代码示例
我直接给你写好可复用的代码,你可以照着改:
1. 基础版:主进程生成帧 + FFmpeg子进程编码推流
import subprocess import sys # 替换成你自己的帧生成逻辑,返回帧的字节/字符串数据 def get_frame(): # 示例:生成模拟的YUV420p格式帧(1920x1080),你换成自己的帧字符串即可 width, height = 1920, 1080 frame_size = width * height * 3 // 2 # YUV420p的帧大小计算 return b'\x00' * frame_size # 用全黑帧模拟,实际替换成你的帧数据 def main(): # 这里是FFmpeg的命令行参数,根据你的直播需求调整 # 核心是`-i -`表示从stdin读取输入,也就是我们主进程传过去的帧 ffmpeg_cmd = [ 'ffmpeg', '-f', 'rawvideo', # 输入格式是原始视频帧 '-pix_fmt', 'yuv420p', # 你的帧格式,要和实际生成的一致 '-s', '1920x1080', # 分辨率,和你的帧匹配 '-r', '30', # 帧率 '-i', '-', # 从stdin读取输入 '-c:v', 'libx264', # 视频编码器 '-preset', 'veryfast', # 编码速度优先,适合直播 '-f', 'flv', # 输出格式,比如FLV对应RTMP 'rtmp://your-live-server-url' # 你的直播推流地址 ] try: # 启动FFmpeg子进程,创建可写入的stdin管道 ffmpeg_proc = subprocess.Popen( ffmpeg_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=False # 帧是二进制数据,用二进制模式传输 ) print("FFmpeg子进程启动成功,开始推流!") # 循环生成并发送帧 while True: frame_data = get_frame() # 把帧数据写入FFmpeg的输入管道 ffmpeg_proc.stdin.write(frame_data) ffmpeg_proc.stdin.flush() # 强制刷新缓存,避免数据积压导致卡顿 except KeyboardInterrupt: print("\n收到中断信号,正在停止推流...") except Exception as e: print(f"出问题了: {str(e)}") # 打印FFmpeg的错误日志,方便调试 if ffmpeg_proc: print("\nFFmpeg错误输出:") print(ffmpeg_proc.stderr.read().decode('utf-8', errors='ignore')) finally: # 优雅关闭子进程,避免资源泄漏 if ffmpeg_proc: ffmpeg_proc.stdin.close() ffmpeg_proc.terminate() ffmpeg_proc.wait() print("推流已停止") if __name__ == "__main__": main()
2. 进阶版:帧生成也放到子进程(适合帧生成CPU密集的场景)
如果你的帧生成逻辑本身也很吃CPU,那可以再用multiprocessing把帧生成单独放到一个子进程,让帧生成、FFmpeg编码两个任务分别占不同核心,彻底榨干CPU性能:
import subprocess from multiprocessing import Process, Queue import sys def frame_generator(queue): # 帧生成逻辑,持续把帧放到队列里 while True: width, height = 1920, 1080 frame_size = width * height * 3 // 2 frame_data = b'\x00' * frame_size # 替换成你的实际帧数据 queue.put(frame_data) def main(): # 创建队列,用于帧生成进程和主进程之间传数据 frame_queue = Queue(maxsize=10) # 设置队列大小,防止内存爆了 # 启动帧生成子进程 gen_proc = Process(target=frame_generator, args=(frame_queue,)) gen_proc.start() # FFmpeg命令行参数,和基础版一致 ffmpeg_cmd = [ 'ffmpeg', '-f', 'rawvideo', '-pix_fmt', 'yuv420p', '-s', '1920x1080', '-r', '30', '-i', '-', '-c:v', 'libx264', '-preset', 'veryfast', '-f', 'flv', 'rtmp://your-live-server-url' ] try: ffmpeg_proc = subprocess.Popen( ffmpeg_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, text=False ) print("双进程启动成功,开始推流!") # 从队列取帧,传给FFmpeg while True: frame_data = frame_queue.get() ffmpeg_proc.stdin.write(frame_data) ffmpeg_proc.stdin.flush() except KeyboardInterrupt: print("\n正在停止所有进程...") except Exception as e: print(f"出错了: {str(e)}") if ffmpeg_proc: print("\nFFmpeg错误输出:") print(ffmpeg_proc.stderr.read().decode('utf-8', errors='ignore')) finally: # 先关帧生成进程,再关FFmpeg gen_proc.terminate() gen_proc.join() if ffmpeg_proc: ffmpeg_proc.stdin.close() ffmpeg_proc.terminate() ffmpeg_proc.wait() print("所有进程已关闭") if __name__ == "__main__": main()
关键注意点
- GIL彻底拜拜:FFmpeg是独立的系统进程,和Python解释器没关系,完全不受GIL限制,能用上所有空闲CPU核心。
- 管道要flush:每次写完帧一定要调用
flush(),不然数据会存在缓存里,导致FFmpeg拿不到数据,直播卡顿。 - 格式要匹配:FFmpeg的输入参数(比如
-pix_fmt、-s、-r)一定要和你生成的帧格式完全一致,不然FFmpeg会报错。
内容的提问来源于stack exchange,提问作者Just Askin
相关产品推荐
相关产品推荐

