ffmpeg直接读取文件推流到rtsp服务器
项目中常常用到推流拉流能功能,那么除了用obs之外,是否可以直接用nodejs、python直接推流呢,答案是肯定的,这里介绍两种使用过的方法,用于个人测试,不够完善,仅仅是抛砖引玉。
首先,下面这条ffmepeg指令即可推流,
ffmpeg -re -stream_loop -1 -i G:\FFOutput\boat-1.mp4 -b:v 1000k -bufsize 1000k -maxrate 1500k -profile:v baseline -g 50 -vf "drawtext=fontfile=E:/FFOutput/simsun.ttf: text='201 channel %{localtime\:%Y\-%m\-%d %H-%M-%S}':fontsize=40:fontcolor=yellow:x=10:y=10" -vcodec h264 -f rtsp rtsp://127.0.0.1:8554/channel02
上面这条命令将会对本地的一个mp4文件进行循环播放,并推流到指定的rtsp服务器,rtsp服务器可以使用开源的easyDarwin。
方案一:python启用子线程使用ffmpeg推流到rtsp服务器
ffmpeg真的非常棒,几乎可以处理任何格式的视频。
下面是python下拉流并推流的一个示例,已经过了实际验证
import cv2
import subprocess
import time
'''拉流url地址,指定 从哪拉流'''
# video_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW) # 自己摄像头
pull_url = 'rtsp://192.168.1.200:554/channel02' # "rtsp_address"
video_capture = cv2.VideoCapture(pull_url) # 调用摄像头的rtsp协议流
# pull_url = "rtmp_address"
'''推流url地址,指定 用opencv把各种处理后的流(视频帧) 推到 哪里'''
# push_url = "rtsp://192.168.107.65:8554/room55"
push_url = "rtsp://192.168.1.200:554/channel04"
width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(video_capture.get(cv2.CAP_PROP_FPS)) # Error setting option framerate to value 0.
print("width", width, "height", height, "fps:", fps)
# command = [r'D:\Softwares\ffmpeg-5.1-full_build\bin\ffmpeg.exe', # windows要指定ffmpeg地址
command = ['ffmpeg', # linux不用指定
'-y', '-an',
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-pix_fmt', 'bgr24', #像素格式
'-s', "{}x{}".format(width, height),
'-r', str(fps), # 自己的摄像头的fps是0,若用自己的notebook摄像头,设置为15、20、25都可。
'-i', '-',
'-c:v', 'libx264', # 视频编码方式
# '-b:v', '1000k',
'-bufsize', '1000k',
'-maxrate', '1000k',
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast',
'-f', 'rtsp', # flv rtsp
'-rtsp_transport', 'udp', # 使用TCP推流,linux中一定要有这行
push_url] # rtsp rtmp
pipe = subprocess.Popen(command, shell=False, stdin=subprocess.PIPE)
def frame_handler(frame):
# ...
cv2.putText(frame, "helloworld", (300, 230), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0))
return frame
process_this_frame = True
while True: # True or video_capture.isOpened():
# Grab a single frame of video
ret, frame = video_capture.read()
# handle the video capture frame
start = time.time()
frame = frame_handler(frame)
# Display the resulting image. linux 需要注释该行代码
# cv2.imshow('Video', frame)
# Hit 'q' on the keyboard to quit!
if cv2.waitKey(delay=100) & 0xFF == ord('q'): # delay=100ms为0.1s .若dealy时间太长,比如1000ms,则无法成功推流!
break
# 存入管道用于直播
pipe.stdin.write(frame.tostring())
# pipe.stdin.write(frame.tobytes())
#out.write(frame) #同时 存入视频文件 记录直播帧数据
video_capture.release()
cv2.destroyAllWindows()
pipe.terminate()
以上代码是通过管道的方式,先是通过cv2去读取源码流的图像,然后处理(包括推理、标记、打标签等一系列操作),然后处理完的帧再塞到管道中,而主线程定义了一个子线程,子线程就是将管道中的图像通过ffmpeg转码推流到rtsp服务器
方案二:python的flask框架输出动态图像到前端显示视频
这个方法非常有意思,直接输出一个页面,而不需要一个流服务器,虽然这样比较占带宽,因为直接传输的是jpg图像,但好就好在使用简单,不需要额外集成,通用性也高,用于demo预览其实是非常合适的
在目录下新建server.py文件,内容如下:
from flask import Flask, render_template, Response
import cv2
import time
class VideoCamera(object):
def __init__(self):
# 通过opencv获取实时视频流
# self.video = cv2.VideoCapture(0)
self.video = cv2.VideoCapture("rtsp://121.196.105.173:13389/channel02")
def __del__(self):
self.video.release()
def get_frame(self):
success, image = self.video.read()
# 在这里处理视频帧
cv2.putText(image, "helloworld", (300, 230), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0))
#
# 因为opencv读取的图片并非jpeg格式,因此要用motion JPEG模式需要先将图片转码成jpg格式图片
ret, jpeg = cv2.imencode('.jpg', image)
return jpeg.tobytes()
app = Flask(__name__)
@app.route('/') # 主页
def index():
# jinja2模板,具体格式保存在index.html文件中
return render_template('index.html')
def gen(camera):
while True:
start_t=time.time()
frame = camera.get_frame()
#print('{0}'.format(time.time()-start_t))
# 使用generator函数输出视频流, 每次请求输出的content类型是image/jpeg
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
@app.route('/video_feed') # 这个地址返回视频流响应
def video_feed():
return Response(gen(VideoCamera()),
mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5000)
再新建目录和文件 ./templates/index.html ,为什么要这么建,是flask模板的默认路径,内同如下
<html>
<head>
<title>Video Streaming Demonstration</title>
</head>
<body>
<h1>Video Streaming Demonstration</h1>
<img src="{{ url_for('video_feed') }}">
</body>
</html>
运行server.py,浏览器输入相应地址即可预览了,真的非常简约。推荐二次开发,可玩性还是可以的。
就我个人而言,对于自己玩玩的小项目,给人稍微展示一下AI效果之类的,可以直接使用方案三,读取-修改-展示一条龙,如果想要更加专业,在企业应用中使用,那么方案二是比较可以的,可以把rtsp流保存到nvr中,便于其它业务调用。
————分割线—–
补充一个框架工具,graio,特别适合布署ai应用,具体方法请参考官网 https://www.gradio.app/