如何使用Streamlink将直播流处理为独立帧,解决白噪声及FFmpeg管道挂起问题
问题原因分析
- 直接读取Streamlink输出乱码的原因:
Streamlink stdout输出的是带封装格式的音视频混合流(常见为FLV、TS格式),既包含视频压缩数据也包含音频数据,绝非原始RGB像素序列,按固定宽高计算字节数读取必然得到乱码。 - 对接FFmpeg后进程挂起的原因:
- FFmpeg启动参数中
stdout被设为None,没有开启输出管道,无法读取解码后的数据 - 手动读取固定字节写入FFmpeg stdin,FFmpeg需要接收足够的流数据才能完成解码输出第一帧,数据量不足会持续等待
- 配置了
vframes=1参数,FFmpeg解码出一帧后就会自动退出,后续读取操作会永久阻塞 - 未处理FFmpeg的stderr输出,缓冲区满后会导致进程挂起
正确实现代码
方案1:直接读取BGR原始帧(适配OpenCV直接处理)
import subprocess import numpy as np import cv2 import time # 自定义配置 STREAM_URL = "你的直播流地址" FRAME_WIDTH = 1920 # 输出帧宽度,可匹配源流分辨率,也可自定义缩放 FRAME_HEIGHT = 1080 # 输出帧高度 EXTRACT_FPS = 1 # 每秒抽帧数量 # 启动Streamlink拉流 streamlink_proc = subprocess.Popen( ["streamlink", STREAM_URL, "best", "-O", "--quiet"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL ) # 启动FFmpeg解码,直接对接Streamlink输出 ffmpeg_proc = subprocess.Popen( [ "ffmpeg", "-i", "pipe:0", "-f", "rawvideo", "-pix_fmt", "bgr24", "-r", str(EXTRACT_FPS), "-s", f"{FRAME_WIDTH}x{FRAME_HEIGHT}", "-" ], stdin=streamlink_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 # 调大缓冲区避免直播卡顿 ) # 循环读帧处理 frame_byte_size = FRAME_WIDTH * FRAME_HEIGHT * 3 while True: frame_bytes = ffmpeg_proc.stdout.read(frame_byte_size) if not frame_bytes: break # 转为OpenCV可用的帧对象 frame = np.frombuffer(frame_bytes, dtype=np.uint8).reshape((FRAME_HEIGHT, FRAME_WIDTH, 3)) # 此处可执行你的自定义逻辑,比如存盘、推理等 cv2.imwrite(f"frames/{int(time.time())}.jpg", frame) # 按Q键退出 if cv2.waitKey(1) & 0xFF == ord('q'): break # 清理进程资源 streamlink_proc.terminate() ffmpeg_proc.terminate() streamlink_proc.wait() ffmpeg_proc.wait()
方案2:读取JPG格式帧(无需固定分辨率)
如果不确定源流分辨率,可让FFmpeg输出JPG格式的帧,无需固定长度读取:
# FFmpeg启动配置替换为以下内容 ffmpeg_proc = subprocess.Popen( [ "ffmpeg", "-i", "pipe:0", "-f", "image2pipe", "-vcodec", "mjpeg", "-r", str(EXTRACT_FPS), "-q:v", "2", # JPG质量,数值越小质量越高 "-" ], stdin=streamlink_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 ) # 读帧逻辑替换为以下内容 while True: jpg_bytes = ffmpeg_proc.stdout.read(1024*1024) # 读取足够大的块 if not jpg_bytes: break frame = cv2.imdecode(np.frombuffer(jpg_bytes, dtype=np.uint8), cv2.IMREAD_COLOR) if frame is None: continue # 跳过不完整的帧 # 后续自定义处理 cv2.imwrite(f"frames/{int(time.time())}.jpg", frame)
内容的提问来源于stack exchange,提问作者Camamber
相关产品推荐
相关产品推荐

