最初利用如下代码进行FFmpeg推流:
import subprocess
import cv2
import numpy as np
import time
class RTMPStreamer:
def __init__(self, rtmp_url, width, height, fps=30):
self.rtmp_url = rtmp_url
self.width = width
self.height = height
self.fps = fps
self.process = None
# FFmpeg命令
self.command = [
'ffmpeg',
'-y', # 覆盖输出文件(如果存在)
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24', # 输入像素格式(OpenCV默认格式)
'-s', f'{width}x{height}', # 视频尺寸
'-r', str(fps), # 帧率
'-i', '-', # 从标准输入读取
'-c:v', 'libx264', # 视频编码器
'-pix_fmt', 'yuv420p', # 输出像素格式(兼容性更好)
'-preset', 'ultrafast', # 快速编码(降低延迟)
'-tune', 'zerolatency', # 零延迟优化
'-f', 'flv', # 输出格式
rtmp_url # RTMP服务器地址
]
def start(self):
"""启动FFmpeg推流进程"""
self.process = subprocess.Popen(
self.command,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=1024
)
def write_frame(self, frame):
"""写入一帧视频数据"""
if self.process is None or self.process.poll() is not None:
raise RuntimeError("Stream not started or already finished")
try:
# 将OpenCV帧(BGR格式)转换为字节流并写入
self.process.stdin.write(frame.tobytes())
except IOError as e:
print(f"Error writing frame: {e}")
self.stop()
def __del__(self):
"""析构函数确保进程被清理"""
self.stop()
# 使用示例
if __name__ == "__main__":
# RTMP服务器地址(替换为你的实际地址)
rtmp_url = "rtmp://server/live/streamkey"
# 视频参数
width, height, fps = 640, 480, 30
# 创建推流器
streamer = RTMPStreamer(rtmp_url, width, height, fps)
streamer.start()
# 模拟生成视频帧(实际应用中可能是从摄像头或视频文件读取)
try:
for _ in range(100): # 推流100帧
# 创建一个彩色测试图案
frame = np.zeros((height, width, 3), dtype=np.uint8)
frame[:, :, 0] = np.linspace(0, 255, width, dtype=np.uint8) # 红色通道渐变
frame[:, :, 1] = np.linspace(0, 255, height, dtype=np.uint8)[:, np.newaxis] # 绿色通道渐变
# 写入帧
streamer.write_frame(frame)
time.sleep(1/fps) # 控制帧率
except KeyboardInterrupt:
print("Streaming interrupted by user")
finally:
self.process = None
首次推流正常,第二次再向相同的RTMP地址推流时发现满屏的Broken Pipe。
刚开始还以为是消费端消费异常导致管道爆掉了,后来排除了这样的可能性,继续往上翻日志,发现了红色的两行字:
[rtmp @ 0x60fad4a938c0] Server error: Already publishing. rtmp://***/live/***:
Operation not permitted
查阅资料表示:
这个错误信息表明你的程序在尝试通过RTMP协议推流时遇到了两个问题:
1、Server error: Already publishing
这表示目标RTMP服务器上已经有一个推流会话使用了相同的流名称。RTMP服务器通常不允许同一流名称被重复使用。
2、Operation not permitted
这可能是由于权限问题、流名称冲突或服务器配置限制导致的。
定位到问题——同一流名称被重复使用,其实就是TCP连接没有正常关闭。有些朋友的解决办法都是每次采用不同的流地址,每次停止推流给流服务器发一个信号,流服务器删除地址,下次推流重新建,但这样有可能会保持多个TCP无效连接。
那怎样能正常关闭呢?不可简单粗暴地将self.process置为None,要在代码中增加一个stop方法:
def stop(self):
"""停止推流"""
if self.process is not None:
if self.process.poll() is None:
self.process.stdin.close()
self.process.wait()
self.process = None
修改后的完整代码如下:
import subprocess
import cv2
import numpy as np
import time
class RTMPStreamer:
def __init__(self, rtmp_url, width, height, fps=30):
self.rtmp_url = rtmp_url
self.width = width
self.height = height
self.fps = fps
self.process = None
# FFmpeg命令
self.command = [
'ffmpeg',
'-y', # 覆盖输出文件(如果存在)
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24', # 输入像素格式(OpenCV默认格式)
'-s', f'{width}x{height}', # 视频尺寸
'-r', str(fps), # 帧率
'-i', '-', # 从标准输入读取
'-c:v', 'libx264', # 视频编码器
'-pix_fmt', 'yuv420p', # 输出像素格式(兼容性更好)
'-preset', 'ultrafast', # 快速编码(降低延迟)
'-tune', 'zerolatency', # 零延迟优化
'-f', 'flv', # 输出格式
rtmp_url # RTMP服务器地址
]
def start(self):
"""启动FFmpeg推流进程"""
self.process = subprocess.Popen(
self.command,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=1024
)
def write_frame(self, frame):
"""写入一帧视频数据"""
if self.process is None or self.process.poll() is not None:
raise RuntimeError("Stream not started or already finished")
try:
# 将OpenCV帧(BGR格式)转换为字节流并写入
self.process.stdin.write(frame.tobytes())
except IOError as e:
print(f"Error writing frame: {e}")
self.stop()
def stop(self):
"""停止推流"""
if self.process is not None:
if self.process.poll() is None:
self.process.stdin.close()
self.process.wait()
self.process = None
def __del__(self):
"""析构函数确保进程被清理"""
self.stop()
# 使用示例
if __name__ == "__main__":
# RTMP服务器地址(替换为你的实际地址)
rtmp_url = "rtmp://server/live/streamkey"
# 视频参数
width, height, fps = 640, 480, 30
# 创建推流器
streamer = RTMPStreamer(rtmp_url, width, height, fps)
streamer.start()
# 模拟生成视频帧(实际应用中可能是从摄像头或视频文件读取)
try:
for _ in range(100): # 推流100帧
# 创建一个彩色测试图案
frame = np.zeros((height, width, 3), dtype=np.uint8)
frame[:, :, 0] = np.linspace(0, 255, width, dtype=np.uint8) # 红色通道渐变
frame[:, :, 1] = np.linspace(0, 255, height, dtype=np.uint8)[:, np.newaxis] # 绿色通道渐变
# 写入帧
streamer.write_frame(frame)
time.sleep(1/fps) # 控制帧率
except KeyboardInterrupt:
print("Streaming interrupted by user")
finally:
streamer.stop()