python利用ffmpeg進行rtmp推流直播

rainweic發表於2019-07-04
思路:

opencv讀取視訊 —> 將視訊分割為幀 —> 將每一幀進行需求加工後 —> 將此幀寫入pipe管道 —> 利用ffmpeg進行推流直播

pipe管道:

啥是pipe管道? 粗略的理解就是一個放共享檔案的地方(理解不是很深刻。。。)
利用這個特點, 把處理後的圖片放入管道, 讓ffmpeg讀取處理後的影像幀並進行rtmp推流即可

程式碼
import subprocess as sp

rtmpUrl = ""
camera_path = ""
cap = cv.VideoCapture(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
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', 
        rtmpUrl]

# 管道配置
p = sp.Popen(command, stdin=sp.PIPE)
        
# read webcamera
while(cap.isOpened()):
    ret, frame = cap.read()
    if not ret:
        print("Opening camera is failed")
        break
            
    # process frame
    # your code
    # process frame
   
    # write to pipe
    p.stdin.write(frame.tostring())

程式碼說明:
  1. rtmpUrl就是要接收視訊的伺服器了, 我做實驗時是在自己機子上配置了一個nginx伺服器接收視訊流(ubuntu 不要通過apt安裝哦, 請從原始碼安裝, 因為apt安裝的版本沒有rtmp協議, 需要下載nginx原始碼然後配合nginx-rtmp-module這個東西安裝 推薦一篇nginx安裝教程)
  2. camera_path就是要進行直播的視訊地址了
  3. 重點的程式碼其實就這幾句:
import subprocess as sp

# ffmpeg command
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', 
        rtmpUrl]

# 管道配置
p = sp.Popen(command, stdin=sp.PIPE)

# write to pipe
p.stdin.write(frame.tostring())
  1. 我讀取的視訊是rtsp網路攝像頭的視訊流, 但是一旦執行沒多久就會出現 pipe broke 的報錯(1080p視訊), 不知道是什麼原因, 若有大神還請指點指點
WriteN, RTMP send error 104 (129 bytes)

更新: 我改1080p為720p 暫時沒出現這個問題

  1. 我在讀取視訊時候還遇到這個報錯
error while decoding xxx

在這裡插入圖片描述
經過一方百度, 是“FFMPEG Lib對在rtsp協議中的H264 vidos不支援”的問題, 解決方法: 程式開啟兩個執行緒, 一個執行緒讀取攝像頭的幀, 另一個執行緒處理這幀圖片, 這裡還推薦一個大佬用佇列處理視訊的方法 ,大家可以套用一下啊 飛機票 多執行緒處理的版本

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應該替換為:
                # cap = cv.VideoCapture(self.camera_path)
                # 因為我這倆天遇到的專案裡出現斷流的毛病
                # 特別是拉取rtmp流的時候!!!!
                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]

  1. 感謝這篇博文提供了新的知識

博主忙著做專案ing 能力也不是很高 歡迎和大家一起討論 但是有的是真不會呀 還請見諒

相關文章