ffmpeg+python在windows平台和linux(ubuntu18.04)上的应用(1)

目录

windows平台

拉流

ffmpeg安装

推流服务器配置

nginx服务器配置

lalserver服务器配置(rtsp)

rtsp推流

Linux平台(ubuntu18.04)

换源

编译ffmpeg

下载源码

安装依赖库

配置并编译

配置环境

虚拟机推流

边缘计算推流

隐蔽的小坑

花屏问题

 最近在工作上需要学习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尽量无损地进行了压制。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值