Python结合ffmpeg实现单线程和多线程推流

作者:

文章目录
  • 在本文中,我们将详细介绍如何使用 Python 进行视频的推流操作。我们将通过两个不同的实现方式,即单线程推流和多线程推流,来展示如何利用 cv2(OpenCV)和 subprocess 等库将视频帧推送到指定的 RTMP 地址。这两种方式都涉及到从摄像头读取视频帧,以及使用 ffmpeg 命令行工具将视频帧进行编码和推流的过程。
  • 以下是单线程推流的代码: import cv2 as cv import subprocess as sp def push_stream(): # 视频读取对象 cap = cv.VideoCapture(0) fps = int(cap.get(cv.CAP_PROP_FPS)) w = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)) ret, frame = cap.read() # 推流地址 rtmpUrl = “rtmp://192.168.3.33:1935/live/” # 推流参数 command = [‘ffmpeg’, ‘-y’, ‘-f’, ‘rawvideo’, ‘-vcodec’,’rawvideo’, ‘-pix_fmt’, ‘bgr24’, ‘-s’, “{}x{}”.format(w, h), ‘-r’, str(fps), ‘-i’, ‘-‘, ‘-c:v’, ‘libx264’, ‘-pix_fmt’, ‘yuv420p’, ‘-preset’, ‘ultrafast’, ‘-f’, ‘flv’, rtmpUrl] # 创建、管理子进程 pipe = sp.Popen(command, stdin=sp.PIPE, bufsize=10 ** 8) # 循环读取 while cap.isOpened(): # 读取一帧 ret, frame = cap.read() if frame is None: print(‘read frame err!’) continue # 显示一帧 cv.imshow(“frame”, frame) # 按键退出 if cv.waitKey(1) & 0xFF == ord(‘q’): break # 读取尺寸、推流 # img=cv.resize(frame,size) pipe.stdin.write(frame) # 关闭窗口 cv.destroyAllWindows() # 停止读取 cap.release() 在这个单线程的实现中,我们执行以下步骤: 初始化视频读取对象: 使用 cv2.VideoCapture(0) 来打开默认的摄像头设备。 获取摄像头的帧率 fps、宽度 w 和高度 h 等参数。 设置推流地址和参数: 定义 rtmpUrl 作为推流的目标地址。 构造 ffmpeg 的命令列表 command,该列表包含了一系列的参数,如 -y 表示覆盖输出文件、-f rawvideo 表示输入格式为原始视频等。 使用 sp.Popen 创建一个子进程,将 ffmpeg 命令作为子进程运行,并且将其输入管道 stdin 连接到我们的程序。 循环读取和推流: 在一个 while 循环中,不断读取摄像头的帧。 若读取失败,打印错误信息并继续。 使用 cv2.imshow 显示当前帧,同时监听 q 键,按下 q 键时退出程序。 将读取到的帧通过管道发送给 ffmpeg 进行推流。
  • 以下是多线程推流的代码: import queue import threading import cv2 as cv import subprocess as sp class Live(object): def __init__(self): self.frame_queue = queue.Queue() self.command = “” # 自行设置 self.rtmpUrl = “” self.camera_path = “” def read_frame(self): print(“开启推流”) cap = cv.VideoCapture(self.camera_path) # Get video information fps = int(cap.get(cv.CAP_PROP_FPS)) width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)) # ffmpeg command self.command = [‘ffmpeg’, ‘-y’, ‘-f’, ‘rawvideo’, ‘-vcodec’,’rawvideo’, ‘-pix_fmt’, ‘bgr24’, ‘-s’, “{}x{}”.format(width, height), ‘-r’, str(fps), ‘-i’, ‘-‘, ‘-c:v’, ‘libx264’, ‘-pix_fmt’, ‘yuv420p’, ‘-preset’, ‘ultrafast’, ‘-f’, ‘flv’, self.rtmpUrl] # read webcamera while(cap.isOpened()): ret, frame = cap.read() if not ret: print(“Opening camera is failed”) break # put frame into queue self.frame_queue.put(frame) def push_frame(self): # 防止多线程时 command 未被设置 while True: if len(self.command) > 0: # 管道配置 p = sp.Popen(self.command, stdin=sp.PIPE) break while True: if self.frame_queue.empty()!= True: frame = self.frame_queue.get() # process frame # 你处理图片的代码 # write to pipe p.stdin.write(frame.tostring()) def run(self): threads = [ threading.Thread(target=Live.read_frame, args=(self,)), threading.Thread(target=Live.push_frame, args=(self,)) ] [thread.setDaemon(True) for thread in threads] [thread.start() for thread in threads] 在这个多线程的实现中,我们使用了 threading 和 queue 库: 类的初始化: 创建一个 Live 类,在 __init__ 方法中初始化帧队列 frame_queue、command、rtmpUrl 和 camera_path 等变量。 读取帧的线程方法: read_frame 方法中,使用 cv2.VideoCapture(self.camera_path) 打开摄像头。 获取摄像头的参数,并构造 ffmpeg 命令。 不断从摄像头读取帧,并将帧放入队列 frame_queue 中。 推流的线程方法: push_frame 方法中,等待 command 被设置,然后使用 sp.Popen 启动 ffmpeg 子进程。 从帧队列中取出帧,并将其写入 ffmpeg 的输入管道进行推流。 启动线程: run 方法创建并启动两个线程,一个用于读取帧,一个用于推流,并且将它们设置为守护线程。
  • 通过上述代码和解释,我们可以看到如何使用 Python 进行单线程和多线程的视频推流操作。单线程代码简单明了,但性能可能受限;多线程代码可以更好地处理高负载,但也需要注意线程安全和资源管理等问题。在实际应用中,我们可以根据具体的需求和硬件性能来选择合适的推流方式。同时,我们可以进一步优化代码,例如添加异常处理、优化帧处理逻辑等,以提高程序的稳定性和性能。 到此这篇关于Python结合ffmpeg 实现单线程和多线程推流的文章就介绍到这了,更多相关Python ffmpeg 单线程和多线程推流内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: windows下python使用ffmpeg实现rtsp推流 Python进行ffmpeg推流和拉流rtsp、rtmp实例详解
  • 目录
    • 一、引言
    • 二、单线程推流
    • 三、多线程推流
    • 四、代码解释和注意事项
      • 单线程推流:
      • 多线程推流:
    • 五、总结

      在本文中,我们将详细介绍如何使用 Python 进行视频的推流操作。我们将通过两个不同的实现方式,即单线程推流和多线程推流,来展示如何利用 cv2(OpenCV)和 subprocess 等库将视频帧推送到指定的 RTMP 地址。这两种方式都涉及到从摄像头读取视频帧,以及使用 ffmpeg 命令行工具将视频帧进行编码和推流的过程。

      以下是单线程推流的代码:

      import cv2 as cv
      import subprocess as sp
      
      
      def push_stream():
          # 视频读取对象
          cap = cv.VideoCapture(0) 
          fps = int(cap.get(cv.CAP_PROP_FPS))
          w = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
          h = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
          ret, frame = cap.read()
          # 推流地址
          rtmpUrl = "rtmp://192.168.3.33:1935/live/"
          # 推流参数
          command = ['ffmpeg',
                    '-y',
                    '-f', 'rawvideo',
                    '-vcodec','rawvideo',
                    '-pix_fmt', 'bgr24',
                    '-s', "{}x{}".format(w, h),
                    '-r', str(fps),
                    '-i', '-',
                    '-c:v', 'libx264',
                    '-pix_fmt', 'yuv420p',
                    '-preset', 'ultrafast',
                    '-f', 'flv', 
                    rtmpUrl]
          # 创建、管理子进程
          pipe = sp.Popen(command, stdin=sp.PIPE, bufsize=10 ** 8)
          # 循环读取
          while cap.isOpened():
              # 读取一帧
              ret, frame = cap.read()
              if frame is None:
                  print('read frame err!')
                  continue
              # 显示一帧
              cv.imshow("frame", frame)
              # 按键退出
              if cv.waitKey(1) & 0xFF == ord('q'):
                  break
              # 读取尺寸、推流
              # img=cv.resize(frame,size)
              pipe.stdin.write(frame) 
          # 关闭窗口
          cv.destroyAllWindows()
          # 停止读取
          cap.release()
      

      在这个单线程的实现中,我们执行以下步骤:

      初始化视频读取对象

      • 使用 cv2.VideoCapture(0) 来打开默认的摄像头设备。
      • 获取摄像头的帧率 fps、宽度 w 和高度 h 等参数。

      设置推流地址和参数

      • 定义 rtmpUrl 作为推流的目标地址。
      • 构造 ffmpeg 的命令列表 command,该列表包含了一系列的参数,如 -y 表示覆盖输出文件、-f rawvideo 表示输入格式为原始视频等。
      • 使用 sp.Popen 创建一个子进程,将 ffmpeg 命令作为子进程运行,并且将其输入管道 stdin 连接到我们的程序。

      循环读取和推流

      • 在一个 while 循环中,不断读取摄像头的帧。
      • 若读取失败,打印错误信息并继续。
      • 使用 cv2.imshow 显示当前帧,同时监听 q 键,按下 q 键时退出程序。
      • 将读取到的帧通过管道发送给 ffmpeg 进行推流。

      以下是多线程推流的代码:

      import queue
      import threading
      import cv2 as cv
      import subprocess as sp
      
      
      class Live(object):
          def __init__(self):
              self.frame_queue = queue.Queue()
              self.command = ""
              # 自行设置
              self.rtmpUrl = ""
              self.camera_path = ""
      
          def read_frame(self):
              print("开启推流")
              cap = cv.VideoCapture(self.camera_path)
      
              # Get video information
              fps = int(cap.get(cv.CAP_PROP_FPS))
              width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
              height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
      
              # ffmpeg command
              self.command = ['ffmpeg',
                             '-y',
                             '-f', 'rawvideo',
                             '-vcodec','rawvideo',
                             '-pix_fmt', 'bgr24',
                             '-s', "{}x{}".format(width, height),
                             '-r', str(fps),
                             '-i', '-',
                             '-c:v', 'libx264',
                             '-pix_fmt', 'yuv420p',
                             '-preset', 'ultrafast',
                             '-f', 'flv', 
                             self.rtmpUrl]
      
              # read webcamera
              while(cap.isOpened()):
                  ret, frame = cap.read()
                  if not ret:
                      print("Opening camera is failed")
                      break
      
                  # put frame into queue
                  self.frame_queue.put(frame)
      
          def push_frame(self):
              # 防止多线程时 command 未被设置
              while True:
                  if len(self.command) > 0:
                      # 管道配置
                      p = sp.Popen(self.command, stdin=sp.PIPE)
                      break
      
              while True:
                  if self.frame_queue.empty()!= True:
                      frame = self.frame_queue.get()
                      # process frame
                      # 你处理图片的代码
                      # write to pipe
                      p.stdin.write(frame.tostring())
      
          def run(self):
              threads = [
                  threading.Thread(target=Live.read_frame, args=(self,)),
                  threading.Thread(target=Live.push_frame, args=(self,))
              ]
              [thread.setDaemon(True) for thread in threads]
              [thread.start() for thread in threads]
      

      在这个多线程的实现中,我们使用了 threading 和 queue 库:

      类的初始化

      • 创建一个 Live 类,在 __init__ 方法中初始化帧队列 frame_queuecommandrtmpUrl 和 camera_path 等变量。

      读取帧的线程方法

      • read_frame 方法中,使用 cv2.VideoCapture(self.camera_path) 打开摄像头。
      • 获取摄像头的参数,并构造 ffmpeg 命令。
      • 不断从摄像头读取帧,并将帧放入队列 frame_queue 中。

      推流的线程方法

      • push_frame 方法中,等待 command 被设置,然后使用 sp.Popen 启动 ffmpeg 子进程。
      • 从帧队列中取出帧,并将其写入 ffmpeg 的输入管道进行推流。

      启动线程

      • run 方法创建并启动两个线程,一个用于读取帧,一个用于推流,并且将它们设置为守护线程。

      • 这种方式相对简单,适合初学者理解。但由于是单线程操作,在处理复杂任务时可能会导致性能瓶颈,特别是在同时进行视频显示、读取和推流的情况下,可能会出现卡顿现象。

      • 利用多线程可以将不同的任务分配给不同的线程,提高性能。
      • frame_queue 是一个线程安全的队列,用于在两个线程之间传递帧数据,避免了数据竞争问题。
      • setDaemon(True) 使得线程在主线程结束时自动终止,防止程序无法正常退出。

      通过上述代码和解释,我们可以看到如何使用 Python 进行单线程和多线程的视频推流操作。单线程代码简单明了,但性能可能受限;多线程代码可以更好地处理高负载,但也需要注意线程安全和资源管理等问题。在实际应用中,我们可以根据具体的需求和硬件性能来选择合适的推流方式。同时,我们可以进一步优化代码,例如添加异常处理、优化帧处理逻辑等,以提高程序的稳定性和性能。

      到此这篇关于Python结合ffmpeg 实现单线程和多线程推流的文章就介绍到这了,更多相关Python ffmpeg 单线程和多线程推流内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • windows下python使用ffmpeg实现rtsp推流
      • Python进行ffmpeg推流和拉流rtsp、rtmp实例详解

      站内搜索