解决:FFmpeg推流时报错:Broken Pipe

最初利用如下代码进行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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值