目录
最近在工作上需要学习ffmpeg的推流,故撰写此文,以备日后之需。
windows平台
既然要推流,那肯定需要先拉流。由于我是刚接触这一块内容,没有使用IP摄像机,用的是USB摄像机,不过基本逻辑与方法大同小异(指在csdn上抄别人代码)。
拉流
拉流用的是opencv包,这里要注意安装的命令应该是pip install opencv-contrib-python 如果输入pip install opencv -python 安装的包在我的pc上不能读取摄像头数据。
通过cv2对摄像机图像信息进行补货,和IP摄像机不同的就是你的rtspurl是数字0或者1,取决于你的电脑有几个摄像头。
capture = cv2.VideoCapture(rtspurl)
ffmpeg安装
既然在windows平台运行,那就不便于源码编译,从http://www.ffmpeg.org官网下载编译好的压缩包就行了,安装这一块站内很多帖子说的很详细。就是点击第一个下载
今天不知道为什么我进不去这个界面,我从站里盗一张图(
下载完就是这个压缩包
找一个文件夹解压,之后把bin目录加入环境变量就行了。至此,windows平台的ffmpeg安装就ok了。
推流服务器配置
nginx服务器配置
刚开始我觉得rtsp和rtmp差不多,百度搜到一个rtmp的推流就用了。rtmp推流需要安装nginx服务器。基本流程如下:
1.下载nginx,下载nginx 1.7.11.3 Gryphon或更高版本,反正得是Gryphon这个。解压到你新建的文件夹里就行。这里举个例子就解压到nginx文件夹里了。不要着急双击nginx.exe,可能会有问题。
http://nginx-win.ecsds.eu/download/nginx
2.下载nginx-rtmp-module,解压后文件夹改名为nginx-rtmp-module,github下载后不是后面有个master嘛。改名后放到nginx文件夹里。
https://github.com/arut/nginx-rtmp-module
3.把nginx文件夹加入到环境变量。
4.进入nginx文件夹,找到conf文件夹里的 nginx.conf 文件并改名为nginx-win.conf。修改里面的配置文件。
#user nobody;
# multiple workers works !
worker_processes 2;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 8192;
# max value 32768, nginx recycling connections+registry optimization =
# this.value * 20 = max concurrent connections currently tested with one worker
# C1000K should be possible depending there is enough ram/cpu power
# multi_accept on;
}
rtmp {
server {
listen 1935;
chunk_size 4000;
application live {
live on;
}
}
}
http {
#include /nginx/conf/naxsi_core.rules;
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr:$remote_port - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
# # loadbalancing PHP
# upstream myLoadBalancer {
# server 127.0.0.1:9001 weight=1 fail_timeout=5;
# server 127.0.0.1:9002 weight=1 fail_timeout=5;
# server 127.0.0.1:9003 weight=1 fail_timeout=5;
# server 127.0.0.1:9004 weight=1 fail_timeout=5;
# server 127.0.0.1:9005 weight=1 fail_timeout=5;
# server 127.0.0.1:9006 weight=1 fail_timeout=5;
# server 127.0.0.1:9007 weight=1 fail_timeout=5;
# server 127.0.0.1:9008 weight=1 fail_timeout=5;
# server 127.0.0.1:9009 weight=1 fail_timeout=5;
# server 127.0.0.1:9010 weight=1 fail_timeout=5;
# least_conn;
# }
sendfile off;
#tcp_nopush on;
server_names_hash_bucket_size 128;
## Start: Timeouts ##
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 30;
send_timeout 10;
keepalive_requests 10;
## End: Timeouts ##
#gzip on;
server {
listen 80;
server_name localhost;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root nginx-rtmp-module/;
}
location /control {
rtmp_control all;
}
#charset koi8-r;
#access_log logs/host.access.log main;
## Caching Static Files, put before first location
#location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
# expires 14d;
# add_header Vary Accept-Encoding;
#}
# For Naxsi remove the single # line for learn mode, or the ## lines for full WAF mode
location / {
#include /nginx/conf/mysite.rules; # see also http block naxsi include line
##SecRulesEnabled;
##DeniedUrl "/RequestDenied";
##CheckRule "$SQL >= 8" BLOCK;
##CheckRule "$RFI >= 8" BLOCK;
##CheckRule "$TRAVERSAL >= 4" BLOCK;
##CheckRule "$XSS >= 8" BLOCK;
root html;
index index.html index.htm;
}
# For Naxsi remove the ## lines for full WAF mode, redirect location block used by naxsi
##location /RequestDenied {
## return 412;
##}
## Lua examples !
# location /robots.txt {
# rewrite_by_lua '
# if ngx.var.http_host ~= "localhost" then
# return ngx.exec("/robots_disallow.txt");
# end
# ';
# }
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000; # single backend process
# fastcgi_pass myLoadBalancer; # or multiple, see example above
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl spdy;
# server_name localhost;
# ssl on;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_timeout 5m;
# ssl_prefer_server_ciphers On;
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!eNULL:!MD5:!DSS:!EXP:!ADH:!LOW:!MEDIUM;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
监听端口还是80,rtmp的端口是默认的1935,想修改的可以自己改。
5.开启服务器
在nginx文件夹的地址栏输入cmd,在弹出的窗口里用命令开启服务器
nginx.exe -c conf\nginx-win.conf
输入之后没有弹出的信息,cmd窗口关了也不会关闭服务器。如果要彻底关闭可以用命令或者去任务管理器里面直接结束进程,一般都会有很多nginx.exe。如果你开启不了服务器也可以看看任务管理器里面有没有进程,都退了再用命令开启。然后在浏览器地址栏输入127.0.0.1如果看到下图就说明开启成功了。
这里有一个小坑,服务器只要打开过一次,在退出之后这个界面还会显示,多刷新两次看看还能不能显示。
lalserver服务器配置(rtsp)
因为我最终目的是进行rtsp推流,用nginx服务器不能推rtsp,只能推rtmp流。于是我在github上找到了一个非常好用的rtsp服务器,作者真的很厉害。
下载地址
https://github.com/q191201771/lal
官方文档
https://pengrl.com/lal/#/?id=%e2%96%a6-%e4%b8%89-lalserver-%e4%bd%bf%e7%94%a8
官方文档写的很详细,我再写就有点关公面前耍大刀的感觉了。 唯一要注意的就是在windows平台运行的时候我们不方便编译,就下载作者编译好的可执行二进制文件。下载好之后,把lalserver改后缀为.exe,然后把conf文件夹放在bin目录下。
在bin文件夹里面执行cmd,输入
lalserver -c conf\lalserver.conf.json
就能开启服务器,非常简单吧。看看官方文档,还能加入鉴权防盗链,直接就把USB摄像机的功能提升到了IP摄像机的高度,好强的作者。
rtsp推流
这个问题站内全都是,直接搬运
https://blog.csdn.net/m0_46825740/article/details/125301952?spm=1001.2014.3001.5502
这个里面用线程的那个思路很好,相当于建立了一个缓冲区。
直接上代码,功能为自动检测屏幕分辨率展示摄像机图像,综合了不少高人的代码用了一下。这里的地址是本机地址,/live/是推流的文件路径,不用管。后面的stream?这里是我用lalserver对推流操作进行了加密,官方文档里都有的。
class Live_stack(object):
def __init__(self):
# 相关参数设置
# 摄像机地址
self.Camera_url = 0
# 推流服务器地址
self.Push_url = '流地址?lalserver密码'
# 设置缓冲栈大小
self.stack_len = 10000
# 设置缓冲栈
self.stack = []
# 接收主机屏幕分辨率
# 获得屏幕分辨率X轴
self.width = win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
# 获得屏幕分辨率Y轴
self.height = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
# 获得屏幕刷新率
self.fps = getattr(win32api.EnumDisplaySettings(win32api.EnumDisplayDevices().DeviceName, -1), 'DisplayFrequency')
# 从摄像机拉流放入缓冲栈
def read_image(self):
capture = cv2.VideoCapture(self.Camera_url)
# capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
try:
if (capture.isOpened()) == 0:
raise ValueError("摄像头未开启")
except ValueError as e:
print("引发异常", repr(e))
capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.width) # 宽度
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height) # 高度
capture.set(cv2.CAP_PROP_FPS, self.fps) # 帧率
# 查看一下视频的大小以及帧率
fps = int(capture.get(cv2.CAP_PROP_FPS))
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
print('接收图像数据帧数为%s,宽度为%s,高度为%s' % (fps, width, height))
# 检查帧是否正常
while True:
# global mutex
ret, frame = capture.read()
try:
if ret == 0:
raise ValueError("读取帧错误")
except ValueError as e:
print("引发异常", repr(e))
# 写入缓冲栈
self.stack.append(frame)
# print(stack)
# 每到一定容量清空一次缓冲栈
# 利用gc库,手动清理内存垃圾,防止内存溢出
if len(self.stack) >= self.stack_len:
del self.stack[:]
gc.collect()
# mutex.release()
# 从缓冲栈推流到rtsp服务器
def push_image(self):
command_out = ['D:/ffmpeg/bin/ffmpeg.exe',
'-y', # 覆盖输出文件不询问
'-f', 'rawvideo', # 输入格式
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24', # 和后面那个加起来的意思是解码颜色再编码
'-s', str(self.width) + '*' + str(self.height), # 设置图像大小
'-r', str(self.fps), # 设置帧数
'-i', '-',
'-c:v', 'libx264', # 视频压缩格式
'-pix_fmt', 'yuv420p',
'-preset', 'slow', # 输出的视频质量,零延迟下输出质量需要调整到最低ultrafast
'-tune', 'zerolatency', # 极大降低延迟,但画面容易中断,甚至打不开imshow命令
'-b:v', '6000k', # 调整码率为10Mbps+
'-bufsize', '100000k', # 加缓冲
'-sc_threshold', '499', # 设置场景更改检测的阈值,值越高画面会越流畅,但是相同码率下画面会变模糊,值过低运动时画面会出现卡顿
'-profile:v', 'high', # 设置画面质量
'-g', '0',
'-crf', '20', # 数字越大,压缩越狠,画质越差
'-f', 'rtsp',
self.Push_url]
p = sp.Popen(command_out, stdin=sp.PIPE, shell=True)
# 判断缓冲栈是否已满或为空
while True:
# global mutex
if len(self.stack) != 0:
if len(self.stack) != 10000 and len(self.stack) != 0:
frame = self.stack.pop()
p.stdin.write(frame.tostring())
else:
print('未接收到摄像机数据')
if len(self.stack) >= self.stack_len:
print('缓冲栈炸了')
del self.stack[:]
gc.collect()
# mutex.acquire()
def run_single_camera(self):
threads = [threading.Thread(target=Live_stack.read_image, args=(self,)), # 开一个进程,把rstp流数据接回来
threading.Thread(target=Live_stack.push_image, args=(self,))] # 处理接到的图像,再推到rtmp服务器上
[thread.start() for thread in threads] # 开启子进程
[thread.join() for thread in threads] # 等待所有子进程运行完毕
if __name__ == '__main__':
LIVE = Live_queue()
LIVE.run_single_camera()
connand_out的配置非常关键,80%的错都出在这了我。ffmpeg的路径我在windows平台下必须是绝对路径才能用。-i上面是解码,下面是编码。因为ffmpeg的原理就是解复用-解码-编码。
这一段代码的shell=True一定要有,这样才能调用命令行启动ffmpeg。
p = sp.Popen(command_out, stdin=sp.PIPE, shell=True)
代码里有加互斥锁,展示的部分注释掉了。推流之后用imshow()去接收看看就好
import cv2
import time
rtmpurl = 'rtmp:'
rtspurl = 'rtsp:'
# 在这里换
capture = cv2.VideoCapture(rtspurl)
while (capture.isOpened()):
ret, frame = capture.read()
try:
if ret == 0:
raise ValueError("读取帧错误")
except ValueError as e:
print("引发异常", repr(e))
#显示图片
# cv2.namedWindow("enhanced", 0)
# cv2.resizeWindow("enhanced", 300, 300)
cv2.imshow("frame", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
#释放内存
capture.release()
cv2.destroyAllWindows()
按q可以退出。
Linux平台(ubuntu18.04)
由于第一次使用布谷鸟ADU502的边缘计算。属实有点难搞。在Linux平台下可以很方便的用源码进行ffmpeg的编译。不过之前需要先做一些准备工作。
换源
默认源是真的慢,安装配置环境本来就要很多,速度慢根本受不了。由于是ARM,换源的时候不要换成x86的了,不然还是不行。另外经过测试,清华源有点小问题,有的包apt还是找不到。所以我换了阿里源。
gedit /etc/apt/sources.list
打开下载源,换成下面的。不过还是建议去阿里源自己找,万一更新了。
deb https://mirrors.aliyun.com/ubuntu-ports/ xenial main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu-ports/ xenial main main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu-ports/ xenial-updates main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu-ports/ xenial-updates main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu-ports/ xenial-backports main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu-ports/ xenial-backports main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu-ports/ xenial-security main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu-ports/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
# deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
然后输入
sudo apt-get update 更新源
sudo apt-get upgrade 更新已安装的包
结束了之后我们就能开始编译ffmpeg了。
编译ffmpeg
下载源码
终端输入,下载源码并解压。
http://git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg
在下载文件保存界面打开终端解压
安装依赖库
sudo apt-get install -y autoconf automake build-essential git libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo wget zlib1g-dev libavformat-dev libavcodec-dev libswresample-dev libswscale-dev libavutil-dev libsdl1.2-dev yasm libsdl2-dev libx264-dev libx265-dev libfdk-aac-dev
有一个库是ffplay,就是ffmpeg的播放器需要安装的依赖库,我安装的时候失败了。不过你如果不用ffplay,完全无伤大雅。
配置并编译
./configure --enable-shared --prefix=./build --enable-gpl --enable-libx264 --enable-libx265
build是编译后的文件夹,名字可以自己改,后面--enable的是你编译之后希望ffmpeg额外具有的编码功能,我需要libx264,必须要加上,所以依赖库里面必须安装libx264-dev。
下面列出编译的选项,源链接
https://blog.csdn.net/m0_51004308/article/details/117528429
安装libx265
H.265/HEVC 视频编码器。版本 ≥ 68。
需要 ffmpeg 编译选项配置 --enable-gpl 和 --enable-libx265。
sudo apt-get install libx265-dev libnuma-dev
安装libvpx
VP8/VP9 视频编码、解码器。版本 ≥ 1.4.0。需要 ffmpeg 编译选项配置 --enable-libvpx。sudo apt-get install libvpx-dev
安装libfdk-aac
AAC 音频编码器。需要 ffmpeg 编译选项配置 --enable-libfdk-aac(如果配置包含了 --enable-gpl 需要同时添加 --enable-nonfree)。sudo apt-get install libfdk-aac-dev
安装libmp3lame
mp3 音频编码器。版本 ≥ 3.98.3。需要 ffmpeg 编译选项配置 --enable-libmp3lamesudo apt-get install libmp3lame-dev
安装libopus
Opus音频解码器和编码器。版本 ≥ 1.1。需要 ffmpeg 编译选项配置 --enable-libopus。`sudo apt-get install libopus-dev
sudo make -j8 # 编译,有几个核后面数字就填几
sudo make install # 安装ffmpeg
配置环境
和在windows环境中一样,需要配置环境才能使用。先增加安装目录的动态链接库
sudo gedit /etc/ld.so.conf
在末尾加上你编译好的ffmpeg的lib路径,按照咱们的流程,路径就是:
/xxx/xxx/build/lib
保存后,输入
sudo ldconfig
这里不设置,会报错找不到lib
然后把bin文件添加到环境变量中
sudo gedit ~/.bashrc
在末尾加入
export PATH="$PATH:/xxx/ffmpeg/build/bin"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/xxx/ffmpeg/build/lib"
最后在终端输入
source ~/.bashrc
激活之后再终端输入ffmpeg,会显示版本信息,就说明搞定了。如果不行,重启终端在试试。
虚拟机推流
有个大佬写了多线程队列的推流方法,非常巧妙。贴一下他的链接。
https://zhuanlan.zhihu.com/p/38136322
在虚拟机环境下和window环境下的最大不同就是摄像机地址和管道的配置,先上代码
import cv2
import time
import multiprocessing as mp
from multiprocessing import Lock
import subprocess as sp
class Live_queue(object):
def __init__(self):
# 相关参数设置
# 摄像机地址
self.Camera_url = '/dev/video0'
# 推流服务器地址
# self.Push_url = 'rtsp://'
self.Push_url = 'rtsp:'
self.width = 1920
# 获得屏幕分辨率Y轴
self.height = 1080
# 获得屏幕刷新率
self.fps = 30
# 生成队列
self.queue = mp.Queue(maxsize=2)
# 接收图像信息
def read_image(self):
# print('从设备获得图像')
capture = cv2.VideoCapture(self.Camera_url)
# capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
# 判断摄像头是否开启
try:
if (capture.isOpened()) == 0:
raise ValueError("摄像头未开启")
except ValueError as e:
print("引发异常", repr(e))
capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.width) # 宽度
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height) # 高度
capture.set(cv2.CAP_PROP_FPS, self.fps) # 帧率
# 查看一下视频的大小以及帧率
fps = int(capture.get(cv2.CAP_PROP_FPS))
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
print('接收图像数据帧数为%s,宽度为%s,高度为%s' % (fps, width, height))
while True:
ret, _ = capture.read()
try:
if ret == 0:
raise ValueError("读取帧错误")
except ValueError as e:
print("引发异常", repr(e))
# 操作队列
self.queue.put(image) # 把获得的图像tensor数据塞进队列
self.queue.get() if self.queue.qsize() > 1 else time.sleep(0.01)
# 如果队列长度大于1,q.get方法会释放队列第一个数据并返回,就是说如果队列的数据还没有被取走,就用get方法删除先接收到的图像,确保接收的是
# 最新的图像
# 图像通过ffmpeg推送
def image_put(self):
# 管道配置
command_out = ['/home/cty/ffmpeg/rebuild/bin/ffmpeg',
'-y', # 覆盖输出文件不询问
'-f', 'rawvideo', # 输入格式
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24', # 显示可用的像素格式
'-s', str(self.width) + '*' + str(self.height), # 设置图像大小
'-r', str(self.fps), # 设置帧数
'-i', '-',
'-c:v', 'libx264',
'-acodec', 'aac',
'-pix_fmt', 'yuv420p',
'-preset', 'fast', # 输出的视频质量,零延迟下输出质量需要调整到最低ultrafast
'-tune', 'zerolatency', # 极大降低延迟,但画面容易中断,甚至打不开imshow命令
'-b:v', '6000k', # 调整码率为10Mbps+
'-bufsize', '1024000k', # 加缓冲
'-sc_threshold', '499', # 设置场景更改检测的阈值,值越高画面会越流畅,但是相同码率下画面会变模糊,值过低运动时画面会出现卡顿
'-profile:v', 'high', # 设置画面质量
'-g', '20',
'-crf', '18', # 数字越大,压缩越狠,画质越差
'-f', 'rtsp',
self.Push_url]
p = sp.Popen(command_out, stdin=sp.PIPE, shell=False)
# 读取视频数据
while True:
frame = self.queue.get()
# 写管子
p.stdin.write(frame.tobytes())
def run_single_camera(self):
# mp.set_start_method(method='spawn', force=True)
processes = [mp.Process(target=Live_queue.read_image, args=(self,)), # 开一个进程,把rstp流数据接回来
mp.Process(target=Live_queue.image_put, args=(self,))] # 处理接到的图像,再推到rtmp服务器上
[process.start() for process in processes] # 开启子进程
[process.join() for process in processes] # 等待所有子进程运行完毕
if __name__ == '__main__':
lock = Lock()
LIVE = Live_queue()
LIVE.run_single_camera()
# 互斥锁
通过以下命令获得摄像机编号
ls /dev/video*
这样就能在linux环境下找到摄像机地址。然后在虚拟机环境下ffmpeg的路径也要是绝对路径,不知道为什么,明明环境变量都添加了。
p = sp.Popen(command_out, stdin=sp.PIPE, shell=False)
还有就是这个里面的shell一定要是False,不然不能运行。
边缘计算推流
这下代码就不一样了,这里也是坑比较多的地方
首先,我在windows平台通过
ffmpeg -list_devices true -f dshow -i dummy
先查看设备名称,然后输入
ffmpeg -list_options true -f dshow -i video="设备名称"
查看你的摄像头支持的格式和分辨率,如下图
我之前设置的1080p,推流只有3帧数。我用top命令发现是一核有难七核围观,我以为是占用太高,用多进程试了一下还是3帧。太神奇了,还好我灵机一动觉得事情不是这么简单,感觉冥冥之中有人在限制我的帧率。一看这个明白了。
由于我们推流前要先拉流,推流的目的是尽量要保持原视频的清晰度,出现低帧数之后通过cv2查看了获得的视频帧数和大小,认为是拉流端出了问题,分析得出摄像机默认编码格式不是mjpeg,于是利用cv2给他在拉流的时候设置一下。
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
这下好了,用多线程就30帧了,但是多进程队列的帧数还是12帧,原因我也不知道。
直接上代码
import cv2
import time
import multiprocessing as mp
import subprocess as sp
import gc
import threading
class Live_stack(object):
def __init__(self):
# 相关参数设置
# 摄像机地址
self.Camera_url = '/dev/video0'
# 推流服务器地址
self.Push_url = 'rtsp://'
# self.Push_url = 'rtsp://'
# 设置缓冲栈大小
self.stack_len = 100
# 设置缓冲栈
self.stack = []
# 接收主机屏幕分辨率
# 获得屏幕分辨率X轴
# self.width = win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
self.width = 1920
# 获得屏幕分辨率Y轴
# self.height = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
self.height = 1080
# 获得屏幕刷新率
# self.fps = getattr(win32api.EnumDisplaySettings(win32api.EnumDisplayDevices().DeviceName, -1), 'DisplayFrequency')
self.fps = 30
# 从摄像机拉流放入缓冲栈
def read_image(self):
capture = cv2.VideoCapture(self.Camera_url)
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
try:
if (capture.isOpened()) == 0:
raise ValueError("摄像头未开启")
except ValueError as e:
print("引发异常", repr(e))
capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.width) # 宽度
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height) # 高度
capture.set(cv2.CAP_PROP_FPS, self.fps) # 帧率
# 查看一下视频的大小以及帧率
fps = int(capture.get(cv2.CAP_PROP_FPS))
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
print('接收图像数据帧数为%s,宽度为%s,高度为%s' % (fps, width, height))
# 检查帧是否正常
while True:
ret, frame = capture.read()
try:
if ret == 0:
raise ValueError("读取帧错误")
except ValueError as e:
print("引发异常", repr(e))
# 写入缓冲栈
mutex.acquire()
self.stack.append(frame)
# print(stack)
# 每到一定容量清空一次缓冲栈
# 利用gc库,手动清理内存垃圾,防止内存溢出
if len(self.stack) >= self.stack_len:
del self.stack[:]
gc.collect()
mutex.release()
# 从缓冲栈推流到rtsp服务器
def push_image(self):
command_out = ['ffmpeg',
'-y', # 覆盖输出文件不询问
'-f', 'rawvideo', # 输入格式
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24', # 和后面那个加起来的意思是解码颜色再编码
'-s', str(self.width) + '*' + str(self.height), # 设置图像大小
'-r', str(self.fps), # 设置帧数
'-i', '-',
'-c:v', 'libx264', # 视频压缩格式
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast', # 输出的视频质量,零延迟下输出质量需要调整到最低ultrafast
'-tune', 'zerolatency', # 极大降低延迟,但画面容易中断,甚至打不开imshow命令
'-b:v', '2000k', # 调整码率为10Mbps+
'-bufsize', '100000k', # 加缓冲
'-sc_threshold', '499', # 设置场景更改检测的阈值,值越高画面会越流畅,但是相同码率下画面会变模糊,值过低运动时画面会出现卡顿
'-profile:v', 'high', # 设置画面质量
'-g', '25',
'-crf', '18', # 数字越大,压缩越狠,画质越差
'-rtsp_transport', 'tcp',
'-f', 'rtsp',
self.Push_url]
p = sp.Popen(command_out, stdin=sp.PIPE, shell=False)
# 判断缓冲栈是否已满或为空
while True:
mutex.acquire()
if len(self.stack) != 0:
if len(self.stack) != 100 and len(self.stack) != 0:
frame = self.stack.pop()
p.stdin.write(frame.tostring())
else:
print('未接收到摄像机数据')
if len(self.stack) >= self.stack_len:
print('缓冲栈炸了')
del self.stack[:]
gc.collect()
mutex.release()
def run_single_camera(self):
# for i in range(1):
# thread_pro = threading.Thread(target=Live_stack.read_image, args=(self,))
# thread_pro.start()
#
# for j in range(1):
# thread_con = threading.Thread(target=Live_stack.push_image, args=(self,))
# thread_con.start()
threads = [threading.Thread(target=Live_stack.read_image, args=(self,)), # 开一个进程,把rstp流数据接回来
threading.Thread(target=Live_stack.push_image, args=(self,))] # 处理接到的图像,再推到rtmp服务器上
[thread.start() for thread in threads] # 开启子进程
[thread.join() for thread in threads] # 等待所有子进程运行完毕
if __name__ == '__main__':
# 互斥锁
mutex = threading.Lock()
# 实例化
live = Live_stack()
live.run_single_camera()
可以看到在ARM上配好的ffmpeg环境就不需要用绝对路径了。
隐蔽的小坑
mp.set_start_method(method='spawn', force=True)
进程设置的代码,在windows平台没问题,在linux平台用的时候请注释,错都不报。
p.stdin.write(frame.tostring())
这句报错一般会提示管道损坏,往上翻一下就能看见其他的错误,一般是说输入输出的编解码格式有错,重新检查ffmpeg的配置格式。看看你传的服务器是什么,传的数据是什么。Ffmpeg的原理是“解复用-解码-编码-复用器”,看一下哪里少了。还有一种情况是配置ffmpeg时候的参数出错了,填了根本没有的参数。
当然,这句代码版本太低了可以换成
p.stdin.write(frame.tobytes())
花屏问题
error while decoding MB 23 54
corrupted macroblock 23 54 (total_coeff=-1)
out of range intra chroma pred mode
top block unavailable for requested intra mode -1
negative number of zero coeffs at 81 60
mb_type 38 in I slice too large at 23 54
cbp too large (69) at 117 52
dquant out of range (-597) at 116 52
基本都会报这个错,我看网上有人说改默认UDP的缓冲大小,我试了,也重新编译了。没啥用,我又看有人说删除解码错误帧,找了半天没有合适教程。我又看见有人说用tcp,我试了一下还真行,就是在 command_out 里面加一句
'-rtsp_transport', 'tcp',
说句实话,就是这玩意儿你发送的太大了,改了tcp也不一定下次还有用。删除错误帧还会引起跳帧。不如直接压缩一下文件
'-crf', '18', # 数字越大,压缩越狠,画质越差
这个参数我调到20就不会花屏,18就会,因为18是ffmpeg尽量无损地进行了压制。