
本文介绍解决 python 中使用两个命名管道(fifo)向 ffmpeg 同时输入音视频流时因阻塞导致程序卡死的问题,核心方案是将音视频管道写入操作分离至独立线程,确保 ffmpeg 能同步读取双输入。
本文介绍解决 python 中使用两个命名管道(fifo)向 ffmpeg 同时输入音视频流时因阻塞导致程序卡死的问题,核心方案是将音视频管道写入操作分离至独立线程,确保 ffmpeg 能同步读取双输入。
在 Linux/macOS 系统中,命名管道(FIFO)具有“同步阻塞”特性:open(..., 'wb') 会一直等待对应读端(即 FFmpeg 的 -i 输入)就绪后才返回。当 FFmpeg 启动时虽同时声明了两个 -i 参数,但其内部输入处理并非完全并行——尤其在未显式启用多路复用读取逻辑时,可能仅先打开第一个管道(视频),导致第二个 open()(音频)无限挂起。
✅ 正确做法是:将两个管道的写入操作分别置于独立线程中执行,确保两者几乎同时打开,从而满足 FFmpeg 对双输入管道的并发读取需求。
以下为完整可运行示例:
import os
import subprocess
import threading
import time
VIDEO_PIPE_NAME = "/tmp/ffmpeg_video_pipe"
AUDIO_PIPE_NAME = "/tmp/ffmpeg_audio_pipe"
# 创建命名管道
os.mkfifo(VIDEO_PIPE_NAME)
os.mkfifo(AUDIO_PIPE_NAME)
# 启动 FFmpeg(注意:-listen 1 仅适用于 HTTP 输出,此处假设为本地测试;若需 HTTP 推流,请确保服务端已就绪)
ffmpeg_cmd = [
"ffmpeg",
"-i", VIDEO_PIPE_NAME,
"-i", AUDIO_PIPE_NAME,
"-c:v", "copy",
"-c:a", "copy",
"-f", "mp4",
"-movflags", "frag_keyframe+empty_moov",
"http://0.0.0.0:8081" # 或替换为文件路径如 "output.mp4" 便于调试
]
ffmpeg_process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT)
# 定义写入函数(模拟数据写入,实际中应从解码器/采集设备持续写入)
def write_to_pipe(pipe_path, data_generator, pipe_name):
try:
with open(pipe_path, 'wb') as pipe:
print(f"[{pipe_name}] 管道已打开,开始写入...")
for chunk in data_generator:
pipe.write(chunk)
pipe.flush()
time.sleep(0.01) # 模拟流式节奏,避免过快压垮管道缓冲区
print(f"[{pipe_name}] 写入完成")
except Exception as e:
print(f"[{pipe_name}] 写入异常: {e}")
# 模拟音视频数据(实际项目中替换为真实帧数据)
def video_data():
# 示例:发送 3 秒 640x480 H.264 Annex B 格式 dummy NALU(仅示意)
yield b'\x00\x00\x00\x01\x67\x42\x80\x1E\x95\xA0\x14\x01\x6E\x50\x00\x00\x03\x00\x01\x00\x00\x03\x00\x3C\x8F\x16\x2E'
yield b'\x00\x00\x00\x01\x68\xCE\x38\x80'
yield b'\x00\x00\x00\x01\x06\x00\x00\x00\x01\x05\x00\x00\x00\x01\x01\x00\x00\x00\x01\x01\x00\x00\x00\x01\x01'
def audio_data():
# 示例:发送 AAC ADTS 头 + 帧(简化示意)
adts_header = bytes([0xFF, 0xF1, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00])
yield adts_header + b'\x00' * 100
# 启动双线程写入
video_thread = threading.Thread(target=write_to_pipe, args=(VIDEO_PIPE_NAME, video_data(), "VIDEO"))
audio_thread = threading.Thread(target=write_to_pipe, args=(AUDIO_PIPE_NAME, audio_data(), "AUDIO"))
video_thread.start()
audio_thread.start()
# 可选:等待写入完成或按需终止(生产环境建议加超时和信号控制)
video_thread.join()
audio_thread.join()
# 清理(注意:FFmpeg 进程需自行管理生命周期)
os.unlink(VIDEO_PIPE_NAME)
os.unlink(AUDIO_PIPE_NAME)
⚠️ 关键注意事项:
-
必须使用线程而非进程:
fork()后子进程无法共享父进程打开的 FIFO 文件描述符,且跨进程同步更复杂; -
避免主线程阻塞在
open():所有open(..., 'wb')必须在子线程中调用; - FFmpeg 版本建议 ≥ 4.4:较新版本对多输入 FIFO 的并发读取稳定性更好;
-
调试技巧:先将输出改为本地文件(如
"output.mp4"),确认音画同步正常后再切回网络流; -
资源释放:务必在退出前关闭管道、清理 FIFO 文件,并妥善终止
ffmpeg_process(如ffmpeg_process.terminate()+wait())。
进阶推荐:可关注即将发布的 ffmpegio 新版,其原生支持多输入管道抽象与线程安全写入,大幅降低手动管理复杂度。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











