多线程FFmpeg转码Icecast2分流:如何将各进程输出重定向至独立文件?
解决方案
针对每个FFmpeg进程输出独立日志的需求,有以下几种可行方案:
方案一:通过ffmpeg-python的run()方法直接重定向输出
修改代码逻辑,为每个电台创建独立日志文件,在调用stream.run()时将stdout和stderr定向到该文件。这种方式最简单直接,无需额外依赖。
修改后的代码示例:
import ffmpeg, csv from threading import Thread # 假设这些全局变量已提前定义 ICECAST2_USER = "your_user" ICECAST2_PASS = "your_pass" ICECAST2_PORT = "8000" BITRATE = "64k" SAMPLE_RATE = 44100 FORMAT = "mp3" CODEC = "libmp3lame" def start_stream(name, mount, source): icecast = f"icecast://{ICECAST2_USER}:{ICECAST2_PASS}@localhost:{ICECAST2_PORT}/{mount}" stream = ( ffmpeg .input(source) .output( icecast, audio_bitrate=BITRATE, sample_rate=SAMPLE_RATE, format=FORMAT, acodec=CODEC, reconnect="1", reconnect_streamed="1", reconnect_at_eof="1", reconnect_delay_max="120", ice_name=name, ice_genre=source ) ) # 生成安全的日志文件名(替换特殊字符避免创建失败) log_filename = f"{name.replace('/', '_').replace(' ', '_')}_ffmpeg.log" # 以追加模式打开日志文件,重定向FFmpeg输出 with open(log_filename, 'a', encoding='utf-8') as log_file: stream.run(stdout=log_file, stderr=log_file, overwrite_output=False) with open('stations.csv', mode='r') as data: for station in csv.DictReader(data): thread = Thread( target=start_stream, args=(station['name'], station['mount'], station['url']) ) thread.start()
方案二:用subprocess捕获输出并写入日志
如果需要更灵活的日志处理(比如实时监控、按日期切割日志),可以将ffmpeg命令导出为列表,用subprocess.Popen启动进程,实时读取输出并写入文件。
示例代码:
import ffmpeg, csv, subprocess from threading import Thread # 全局变量同方案一 def start_stream(name, mount, source): icecast = f"icecast://{ICECAST2_USER}:{ICECAST2_PASS}@localhost:{ICECAST2_PORT}/{mount}" stream = ( ffmpeg .input(source) .output( icecast, audio_bitrate=BITRATE, sample_rate=SAMPLE_RATE, format=FORMAT, acodec=CODEC, reconnect="1", reconnect_streamed="1", reconnect_at_eof="1", reconnect_delay_max="120", ice_name=name, ice_genre=source ) ) # 导出FFmpeg命令列表 cmd = stream.compile() log_filename = f"{name.replace('/', '_').replace(' ', '_')}_ffmpeg.log" with open(log_filename, 'a', encoding='utf-8') as log_file: # 启动子进程,合并stdout和stderr proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 # 行缓冲,保证实时输出 ) # 逐行读取并写入日志 for line in proc.stdout: log_file.write(line) log_file.flush() # 立即写入磁盘,避免缓冲区积压 proc.wait() with open('stations.csv', mode='r') as data: for station in csv.DictReader(data): thread = Thread( target=start_stream, args=(station['name'], station['mount'], station['url']) ) thread.start()
方案三:利用FFmpeg原生日志参数
如果你的FFmpeg版本支持-logfile参数(通常4.0+版本可用),可以直接在输出配置中指定日志文件路径,让FFmpeg自行写入日志。
示例代码:
import ffmpeg, csv from threading import Thread # 全局变量同方案一 def run(name, mount, source): icecast = f"icecast://{ICECAST2_USER}:{ICECAST2_PASS}@localhost:{ICECAST2_PORT}/{mount}" log_filename = f"{name.replace('/', '_').replace(' ', '_')}_ffmpeg.log" stream = ( ffmpeg .input(source) .output( icecast, audio_bitrate=BITRATE, sample_rate=SAMPLE_RATE, format=FORMAT, acodec=CODEC, reconnect="1", reconnect_streamed="1", reconnect_at_eof="1", reconnect_delay_max="120", ice_name=name, ice_genre=source, loglevel="info", # 指定日志级别,可选debug/info/warn等 logfile=log_filename # 指定日志文件路径 ) ) return stream with open('stations.csv', mode='r') as data: for station in csv.DictReader(data): stream = run(station['name'], station['mount'], station['url']) thread = Thread(target=stream.run) thread.start()
注意事项
- 日志文件名要避免特殊字符(比如
/、空格),防止创建文件失败 - 如果需要长期运行,建议定期清理或归档日志文件,避免磁盘空间占用过高
- 方案二的实时输出方式需要处理子进程退出逻辑,避免线程僵死
内容的提问来源于stack exchange,提问作者Marcin Kamienski
相关产品推荐
相关产品推荐

