通过NiceGUI调用子进程时FFmpeg崩溃问题排查
问题:NiceGUI触发FFmpeg后台进程失败,直接运行正常
我希望通过NiceGUI按钮触发一个长时间运行的FFmpeg进程,编写了如下代码:
from concurrent.futures import ProcessPoolExecutor import shlex, subprocess import argparse import asyncio from nicegui import ui, app def run_cmd(): cmd = "ffmpeg -y -i data/video.mp4 -acodec libmp3lame -ab 128000 data/video.mp3" subprocess.call(shlex.split(cmd)) pool = ProcessPoolExecutor() async def async_run(): loop = asyncio.get_running_loop() await loop.run_in_executor(pool, run_cmd) args = argparse.ArgumentParser() args.add_argument("--webui", action="store_true") args = args.parse_args() if args.webui: ui.button('Translate', on_click=async_run) app.on_shutdown(pool.shutdown) ui.run() else: run_cmd()
直接运行Python脚本时FFmpeg工作正常,但通过NiceGUI启动(执行python3 ./main.py --webui)时,FFmpeg出现错误并崩溃,错误信息如下:
... Error while decoding stream #0:1: Invalid argument [aac @ 0x555e54100440] Sample rate index in program config element does not match the sample rate index configured by the container. [aac @ 0x555e54100440] Inconsistent channel configuration. [aac @ 0x555e54100440] get_buffer() failed ... [aac @ 0x555e54100440] Error decoding AAC frame header. Error while decoding stream #0:1: Error number -50531338 occurred [aac @ 0x555e54100440] channel element 2.8 is not allocated Error while decoding stream #0:1: Invalid data found when processing input [aac @ 0x555e54100440] Pulse data corrupt or invalid. Error while decoding stream #0:1: Invalid data found when processing input ffmpeg: psymodel.c:576: calc_energy: Assertion `el >= 0' failed. ...
请问我哪里操作有误?或者是否有更简便的方式在NiceGUI中启动后台长进程?
解答
问题原因
- ProcessPoolExecutor环境冲突:ProcessPoolExecutor创建的子进程会复制NiceGUI Web服务的运行环境,而Web服务的IO资源、进程上下文和直接运行脚本时不同,导致FFmpeg解码时出现数据错乱。
- 未隔离FFmpeg的IO流:默认
subprocess.call会继承主进程的stdin/stdout/stderr,Web服务的IO流被占用后,会干扰FFmpeg的解码逻辑,引发缓冲区错误或数据解析异常。
修正方案
方案1:改用线程池+隔离IO流
线程池足够处理外部进程调用,避免进程池的环境隔离问题,同时重定向FFmpeg的IO流,避免干扰Web服务:
from concurrent.futures import ThreadPoolExecutor import shlex, subprocess import argparse import asyncio from nicegui import ui, app def run_cmd(): cmd = "ffmpeg -y -i data/video.mp4 -acodec libmp3lame -ab 128000 data/video.mp3" # 将FFmpeg输出重定向到/dev/null,避免占用Web进程IO with open('/dev/null', 'w') as dev_null: subprocess.call( shlex.split(cmd), stdout=dev_null, stderr=dev_null, stdin=subprocess.DEVNULL ) pool = ThreadPoolExecutor() async def async_run(): loop = asyncio.get_running_loop() await loop.run_in_executor(pool, run_cmd) args = argparse.ArgumentParser() args.add_argument("--webui", action="store_true") args = args.parse_args() if args.webui: ui.button('Convert to MP3', on_click=async_run) app.on_shutdown(pool.shutdown) ui.run() else: run_cmd()
方案2:用asyncio.to_thread简化代码(Python3.9+)
无需手动创建线程池,直接用Python内置的asyncio.to_thread运行同步函数,代码更简洁:
import shlex, subprocess import argparse import asyncio from nicegui import ui, app def run_cmd(): cmd = "ffmpeg -y -i data/video.mp4 -acodec libmp3lame -ab 128000 data/video.mp3" with open('/dev/null', 'w') as dev_null: subprocess.call( shlex.split(cmd), stdout=dev_null, stderr=dev_null, stdin=subprocess.DEVNULL ) async def async_run(): await asyncio.to_thread(run_cmd) args = argparse.ArgumentParser() args.add_argument("--webui", action="store_true") args = args.parse_args() if args.webui: ui.button('Convert to MP3', on_click=async_run) ui.run() else: run_cmd()
核心注意事项
- 必须隔离FFmpeg的IO流:要么重定向到空设备/日志文件,要么通过
subprocess.PIPE读取输出,绝对不能让FFmpeg复用Web服务的IO流。 - 优先选择线程池:FFmpeg本身是独立进程,Python侧不需要额外创建进程,线程池更轻量且不会引发环境冲突。
内容的提问来源于stack exchange,提问作者user1113159
相关产品推荐
相关产品推荐

