使用树莓派基于FFmpeg推流视频和摄像头到B站直播间


前提条件
1、首先要有一个树莓派,并连接了摄像头,且能够访问网络。
2、在Bilibili中通过实名认证,并开通了直播间。(很简单的,实名认证通过后直接就可以开通了)。
3、FFmpeg。推流默认使用FFmpeg,树莓派官方系统默认安装FFmpeg了的,可以使用ffpmeg -version查看详细信息。

从B站直播间获取rtmp地址和直播码

申请了B站直播间后,用电脑开启直播,一定要用电脑才能获取rtmp地址和直播码,用手机直播姬开播无法获取rtmp地址和直播码。
点击开始直播后会出现rtmp地址和直播码(如下图),把rtmp地址和直播码拼接起来就是后面ffpmeg推流的地址。
在这里插入图片描述

在终端使用ffpmeg进行视频或摄像头推流

以下是树莓派Raspbian系统中推流命令:

#推流USB摄像头(包含音频,音频源为音频文件)
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280720 -i “视频源” -stream_loop -1 -i “音频源” -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 10M -bufsize 10M -acodec aac -ac 2 -ar 44100 -ab 128k -f flv “推流地址”

#推流USB摄像头(包含音频,音频源麦克风)
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280720 -i “视频源” -i “音频源” -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 10M -bufsize 10M -acodec aac -ac 2 -ar 44100 -ab 128k -f flv “推流地址”

#推流USB摄像头(不包含音频)
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280720 -i “视频源” -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 10M -bufsize 10M -an -f flv “推流地址”

#推流视频
ffmpeg -re -i “视频源” -vcodec copy -acodec aac -b:a 192k -f flv “推流地址”

树莓派摄像头推流示例:

ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280720 -i “/dev/video0” -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 10M -bufsize 10M -an -f flv “rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_43116538_85852610&key=829133b26b0e1111898aac62df55dcf3&schedul=rtmp”

其中/dev/video0 是树莓派摄像头设备节点,作为推流的输入源,flv 后面那串是B站直播间的rtmp地址加上直播码。
树莓派视频推流示例

ffmpeg -re -i “/home/pi/Desktop/python代码/MP4/video.mp4” -vcodec copy -acodec aac -b:a 192k -flvflags no_duration_filesize -f flv “rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_43116538_85852610&key=829133b26b0e1111898aac62df55dcf3&schedul=rtmp”

以下是Windows系统中推流命令

#推流USB摄像头(包含音频,音频源为音频文件)
ffmpeg -f dshow -s 1280720 -r 1024 -i video=“视频源” -stream_loop -1 -i “音频源” -vcodec libx264 -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 5000k -acodec aac -ac 2 -ar 44100 -ab 128k -preset:v ultrafast -tune:v zerolatency -f flv “推流地址”

#推流USB摄像头(包含音频,音频源麦克风)
ffmpeg -f dshow -s 1280720 -r 1024 -i video=“视频源” -i “音频源” -vcodec libx264 -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 5000k -acodec aac -ac 2 -ar 44100 -ab 128k -preset:v ultrafast -tune:v zerolatency -f flv “推流地址”

#推流USB摄像头(不包含音频)
ffmpeg -f dshow -s 1280720 -r 1024 -i video=“视频源” -vcodec libx264 -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 5000k -an -preset:v ultrafast -tune:v zerolatency -f flv “推流地址”

#推流视频
ffmpeg -re -i “视频源” -vcodec copy -acodec aac -b:a 192k -f flv “推流地址”

以上命令没有经过仔细测试,不同环境需要不同的参数,请自行学习FFmpeg的用法。

FFmpeg基本用法:

ffmpeg [全局选项] {[输入文件选项] -i 输入文件} … {[输出文件选项] 输出文件} …

参数简单介绍:

-f:输入格式(video4linux2 )
-i:输入源
-s:视频分辨率
-r:所需的帧率
-vcodec:视频编解码器
-vb:视频比特率
-bufsize:缓冲区大小(对于流来说很重要)
-vf:像素格式
-g:GOP(图片组,对于流式传输很重要)
-an:不使用音频
-f:输出格式

ffpmeg文档

用python实现控制树莓派推流

上面的推流命令是在树莓派终端执行的,那么如何通过运行python程序来进行摄像头推流呢?
我们可以通过python程序调用sh脚本来执行推流命令。python调用shell脚本的方法有很多种,大家可以自己在网上查,我主要尝试了两种:
一、os.system(“command”)方法
这种方法执行成功会返回一个0。
二、os.popen(“command”)方法
这种方法的返回值是一个文件对象,可以通过read()方法获取返回值。例如:

f=popen("sh ./ffmpeg.sh")
print(f.read())

首先,我们新建一个文件,保存为ffmpeg.sh,然后编辑如下内容,保存:

chmod 777 ./ffmpeg.sh   #使脚本具有执行权限
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280*720 -i "/dev/video0" -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280*720 -g 60 -b:v 10M -bufsize 10M -an -f flv "rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_43116538_85852610&key=829133b26b0e1111898aac62df55dcf3&schedul=rtmp"

python调用shell脚本执行推流代码如下:

import os
os.popen("sh /home/pi/Desktop/python代码/ffmpeg/ffmpeg.sh")

如何停止树莓派推流

在尝试用python调用shell脚本进行摄像头推流时,发现了一个问题,就是不知道怎么停止摄像头推流。
摄像头推流不像视频推流,视频推流在把视频推流完后就结束了,但是摄像头推流会一直进行下去,甚至把python代码停止关闭后脚本依然在后台运行,另外,把B站直播间关闭后推流命令仍然在运行,把python程序和B站直播间关闭后,再次打开B站直播间还是能够看到摄像头画面。除非重启树莓派,否则推流指令就会一直在执行。
因为我想要的效果是开始推流和结束推流都能通过代码来实现,所以就在网上找如何通过一个指令或者代码停止推流,但是找了很久都没有找到具体的方法(如果你们找到了或者有更简单的方法,还请不吝赐教)。以下是我解决问题的过程:
一、首先我在网上查找资料学习如何终止一个脚本,知道可以通过获取其进程的pid,然后用kill -9 pid的方法杀死进程。于是我就想办法和获取sh脚本pid。方法如下:
首先在sh脚本中通过$$获取脚本本身的pid,然后返回给python代码,供python获取,python获取脚本pid后,调用kill函数杀死对应进程。
shell脚本:

chmod 777 ./ffmpeg.sh   #使脚本具有执行权限
echo $$ 
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280*720 -i "/dev/video0" -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280*720 -g 60 -b:v 10M -bufsize 10M -an -f flv "rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_431165318_85852610&key=829133b26b0e1111898aac62df55dcf3&schedule=rtmp"

python代码:

import os
import signal
import time
p=os.popen("sh /home/pi/Desktop/python代码/ffmpeg/ffmpeg.sh")
pid=int(p.read()) #返回的pid是字符串,要进行强制类型转换
time.sleep(20)
os.kill(pid,signal.SIGKILL) #第二个参数表示强制杀死进程

但是运行代码之后还是停止不了推流,经检查后发现如果推流成功,存程序会卡在第四行,即调用sh脚本那一行代码。我试过在那一行代码后面进行一个输出,但是运行程序后并没有对应输出,就相当于程序阻塞在了那里,不断进行推流指令,后面的任何程序都无法执行。所以又进行了另一种方法。
二、把推流命令放在一个子进程或子线程中执行,在主进程或主线程中进行kill命令,我选择的是在子线程中执行。
首先修改sh脚本,因为执行推流指令后无法执行后面代码,所以不能直接通过返回值获取pid,要把脚本pid保存在一个文件(/home/pi/Desktop/python代码/ffmpeg/ffmpeg.pid)中,然后主函数就从文件中获取pid。
sh脚本:

chmod 777 ./ffmpeg.sh   #使脚本具有执行权限
echo $$ > /home/pi/Desktop/python代码/ffmpeg/ffmpeg.pid
ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280*720 -i "/dev/video0" -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280*720 -g 60 -b:v 10M -bufsize 10M -an -f flv "rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_431165318_85852610&key=829133b26b0e1111898aac62df55dcf3&schedule=rtmp"

python代码:

import os
import sys
import time
import signal
import threading
pid=-3

def doffmpeg():
    os.popen("sh /home/pi/Desktop/python代码/ffmpeg/ffmpeg.sh")

if __name__=='__main__':
    thread=threading.Thread(target=doffmpeg)
    thread.start() #开启一个新线程,和开启进程差不多
    #process=Process(target=doffmpeg)
    #process.start()

    time.sleep(20)
    f=open('/home/pi/Desktop/python代码/ffmpeg/ffmpeg.pid') #从文件中读取pid
    pid=int(f.read()) #因为读取出来的是字符串,所以要进行强制类型转换
    print("child_pid=", pid)
	os.kill(pid,signal.SIGKILL) #第二个参数表示强制杀死进程

    main_pid=os.getpid()  #python程序pid
    print("main_pid=", main_pid)

但是,运行程序并且执行了kill指令后,仍然是无法停止摄像头推流,经和同学讨论后,觉得是pid不正确,推流指令可能是又开设了一个新的进程或线程来执行的。于是,我就在终端用ps -efps -aux指令(ef或aux表示查看全部进程)查看全部进程,发现还真是这样:
在这里插入图片描述
由图中可以看出,我们获得的pid和脚本的pid是相同的,但是和推流指令的pid却不一样,并且推流指令的ppid是脚本的pid,说明脚本在执行推流指令时确实是新开了一个进程或者线程。那么如何获得推流指令的pid呢?
三、之后我学习了一些终端指令,发现可以通过进程名称来获取进程pid。
命令 ps
用标准语法查看系统上的每一个进程

ps -e
ps -ef
ps -eF
ps -ely

使用BSD语法查看系统上的每个进程

ps ax
ps axu

-a:显示所有进程(包括其他用户的进程)
-u:用户以及其他详细信息
-x:显示没有控制终端的进程

ps 命令参数介绍

-e: Select all processes. Identical to -A.
-a:显示所有进程(包括其他用户的进程)
-u:用户以及其他详细信息
-x:显示没有控制终端的进程

如何按名称或PID查找一个进程

#名称
ps -ef|grep tomcat
#PID
ps -ef|grep 17850

最简单的方法是使用 pgrep:

pgrep -f name

如果需要查找到 pid 之后 kill 掉该进程,还可以使用 pkill:

pkill -f name

经和同学讨论,我们觉得通过进程名称获取pid是匹配进程名称的关键字的,如下图:
在这里插入图片描述
因为我试着直接用推流指令当作名称匹配时出错了,所以我们只好在以下推流指令中找出独一无二、在其他进程或指令中没有的字符串,应该就能准确匹配到我们想要的指令的pid

ffmpeg -thread_queue_size 512 -f video4linux2 -s 1280720 -i “/dev/video0” -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280720 -g 60 -b:v 10M -bufsize 10M -an -f flv “rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_431165318_85852610&key=829133b26b0e1111898aac62df55dcf3&schedule=rtmp”

这里我选取的字符串是/dev/video0,如下图:
在这里插入图片描述
发现成功获取推流指令pid。因此,我们可以直接用指令:

pkill -f /dev/video0

通过匹配进程名称的方法kill掉推流进程,而无需再获取pid了。
把上述指令写进另一个shell脚本,想要kill掉推流指令就调用此脚本即可。
shell脚本:

chmod 777 ./stopffmpeg.sh
pkill -f /dev/video0
echo "stop ffmpeg"

python代码:

import os
import sys
import time
import signal
import threading
from multiprocessing import Process

def doffmpeg(pid):
    os.popen("sh /home/pi/Desktop/python代码/ffmpeg/ffmpeg.sh")

if __name__=='__main__':
    thread=threading.Thread(target=doffmpeg,args=(pid,))
    thread.start()

    time.sleep(20)
    os.popen("sh /home/pi/Desktop/python代码/ffmpeg/stopffmpeg.sh")

最后实现的功能勉强达到了,我感觉我的方法不太正规,但也找不到更好地方法了,如果有谁又更好的方法,欢迎留言,谢谢!

猜你喜欢

转载自blog.csdn.net/qq_50866711/article/details/113839586