基于RTMP协议流媒体直播的整体解决方案

前言

随着自媒体的短视频和直播带货的流行,再加上这几年疫情的肆虐,直接把实体店给干趴下了。叫苦连天的实体店老板们,原来是不懂这些东西,而被割了韭菜。当然这只是开个玩笑,哈哈。他们没必要懂技术。今天我就来剖析直播的前端和后端的实现流程,干货满满,不要忘了先点个小赞,谢谢了。那么就开始我们今天要讲的内容。

后端

先讲后端,想看Android端实现的稍安勿躁。

RTMP协议

RTMP(Real-Time Messaging Protocol)是一种用于在互联网上传输音频、视频和数据的协议。它最初由Macromedia开发,后来由Adobe Systems继续开发和维护。RTMP最常用于实时数据传输,特别是在流媒体领域中。它被广泛用于在线直播、视频会议和其他实时通信应用中。

RTMP流媒体服务器搭建

首先,我们需要购置一台ECS。
操作系统:推荐Linux(CentOS 7.6 64位)
配置:1核CPU、512M内存、2Mbps服务器带宽(这是能流畅测试的最低配置,1Mbps丢帧会比较严重)。
软件环境:nginx、nginx-rtmp-module。

Nginx的反向代理和负载均衡

有人问我,Nginx是什么?Nginx是一个反向代理程序。既然有反向,那肯定就有正向咯!你怎么这么聪明。那么,什么是正向代理呢?简言之,正向代理就是代理我们客户端的请求,我们经常科学上网用的VPN软件就属于正向代理。反向代理恰恰相反,代理的是我们服务端接收请求。为什么还需要反向代理呢?这个问题问得好。我们客户端不也通常喜欢做拦截,插入一些业务逻辑吗?比如AOP。那么服务端是不是也喜欢搞这些框架的事情,反正都帮你考虑好了,你开箱即用就可以了。不是开棺啊,是开箱即用。如果所有的客户端请求都由同一个节点处理,就算这台服务器节点的性能再好,是不是也可能被打宕机。这是肯定的啊,你一天工作10几个小时,周末还加班,身体肯定也撑不住啊。超过一定的负载阈值,量变就是产生质变。Nginx就是把请求接过来,自己不处理,然后分配给其他的节点处理,称为服务器集群。Nginx来实时监测其他节点的负载状况,公平的分配任务。

安装所需依赖

安装Nginx前,需要先把这几个软件安装好。

yum install -y pcre pcre-devel zlib zlib-devel openssl openssl-devel
下载Nginx
mkdir nginx
cd /nginx
wget https://dorachat-sdk.oss-cn-hongkong.aliyuncs.com/nginx-1.9.11.tar.gz
tar zxvf nginx-1.9.11.tar.gz
下载Nginx的RTMP模块
wget https://dorachat-sdk.oss-cn-hongkong.aliyuncs.com/nginx-rtmp-module-1.2.2.zip
unzip nginx-rtmp-module-1.2.2.zip
编译

将Nginx的RTMP模块和Nginx的源码一起编译。

cd nginx-1.9.11
./configure --add-module=/root/nginx/nginx-rtmp-module-1.2.2  
make  
make install

然后我们查看下是否编译成功。nginx的默认安装目录/usr/local/nginx
里面的sbin目录下有个nginx主程序,启动它。

sudo ./nginx -t

查看是否启动。

ps -ef | grep nginx
配置RTMP

回到Nginx安装目录,修改nginx配置添加rtmp。

sudo vi conf/nginx.conf

nginx的配置文件内容,你覆盖成以下的内容就好。

# events属于Nginx配置范畴,不属于rtmp配置范畴
events {
   
    worker_connections  8192;
}

rtmp {
   
   server {
   
         listen 1935;
         #server_name dorachat.com;
         chunk_size 4096;
         application live {
   
             live on;
             record off;
             # 设置推流和拉流鉴权地址
             # on_publish http://127.0.0.1:8686/auth; 
             # on_play http://127.0.0.1:8686/auth; 
             wait_key on; #对视频切片进行保护,这样就不会产生马赛克了。
             hls_path /opt/live/hls; #切片视频文件存放位置。
             hls_fragment  600s;     #设置HLS片段长度。
             hls_playlist_length 10m;  #设置HLS播放列表长度,这里设置的是10分钟。
             hls_continuous on; #连续模式。
             hls_cleanup on;    #对多余的切片进行删除。
             hls_nested on;     #嵌套模式。
         }
     }
}

我们的流媒体服务器监听的是1935端口,所以阿里云的安全组开放入方向的1935端口。最后我们重新加载nginx的配置。

sudo ./sbin/nginx -s reload

对于高并发和负载均衡我这里就不细说了,有兴趣的可以自行研究upstream和proxy_pass的配置。

测试流媒体服务器是否搭建成功
下载OBS进行推流

OBS官网 https://obsproject.com/welcome 。

⚠️:推流地址设置 rtmp://16.62.162.36:1935/live/home,如live为配置中的application live,home为推流码。home你也可以改成userId。

下载VLC(RTMP播放器)

VLC下载地址 https://dorachat-sdk.oss-cn-hongkong.aliyuncs.com/vlc-3.0.20-intel64.dmg 。

Android端

Android端的推流我们需要使用到NDK,而拉流播放就简单了,使用google官方的ExoPlayer播放器进行播放即可。ExoPlayer官方中文文档https://developer.android.com/media/media3/exoplayer?hl=zh-cn 。

Java层

在Java层,我们做一些编码推流的流程控制。

代码实现

CameraHelper.kt

package site.doramusic.app.live

import android.app.Activity
import android.graphics.ImageFormat
import android.hardware.Camera
import android.hardware.Camera.CameraInfo
import android.hardware.Camera.PreviewCallback
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder

class CameraHelper(
    private val activity: Activity,
    private var cameraId: Int,
    private var width: Int,
    private var height: Int
) :
    SurfaceHolder.Callback, PreviewCallback {
   
    private var camera: Camera? = null
    private var buffer: ByteArray? = null
    private var surfaceHolder: SurfaceHolder? = null
    private var previewCallback: PreviewCallback? = null
    private var rotation = 0
    private var onChangedSizeListener: OnChangedSizeListener? = null
    var bytes: ByteArray? = null

    fun switchCamera() {
   
        cameraId = if (cameraId == CameraInfo.CAMERA_FACING_BACK) {
   
            CameraInfo.CAMERA_FACING_FRONT
        } else {
   
            CameraInfo.CAMERA_FACING_BACK
        }
        stopPreview()
        startPreview()
    }

    private fun stopPreview() {
   
        // 预览数据回调接口
        camera?.setPreviewCallback(null)
        // 停止预览
        camera?.stopPreview()
        // 释放摄像头
        camera?.release()
        camera = null
    }

    private fun startPreview() {
   
        try {
    
            // 获得camera对象
            camera = Camera.open(cameraId)
            // 配置camera的属性
            val parameters = camera!!.getParameters()
            // 设置预览数据格式为nv21
            parameters.previewFormat = ImageFormat.NV21
            // 这是摄像头宽、高
            setPreviewSize(parameters)
            // 设置摄像头 图像传感器的角度、方向
            setPreviewOrientation(parameters)
            camera!!.setParameters(parameters)
            buffer = ByteArray(width * height * 3 / 2)
            bytes = ByteArray(buffer!!.size)
            // 数据缓存区
            camera!!.addCallbackBuffer(buffer)
            camera!!.setPreviewCallbackWithBuffer(this)
            // 设置预览画面
            camera!!.setPreviewDisplay(surfaceHolder)
            camera!!.startPreview()
        } catch (ex: Exception) {
   
            ex.printStackTrace()
        }
    }

    private fun setPreviewOrientation(parameters: Camera.Parameters) {
   
        val info = CameraInfo()
        Camera.getCameraInfo(cameraId, info)
        rotation = activity.windowManager.defaultDisplay.rotation
        var degrees = 0
        when (rotation) {
   
            Surface.ROTATION_0 -> {
   
                degrees = 0
                onChangedSizeListener!!.onChanged(height, width)
            }
            Surface.ROTATION_90 -> {
   
                degrees = 90
                onChangedSizeListener!!.onChanged(width, height)
            }
            Surface.ROTATION_270 -> {
   
                degrees = 270
                onChangedSizeListener!!.onChanged(width, height)
            }
        }
        var result: Int
        if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
   
            result = (info.orientation + degrees) % 360
            result = (360 - result) % 360 // compensate the mirror
        } else {
    // back-facing
            result = (info.orientation - degrees + 360) % 360
        }
        // 设置角度
        camera!!.setDisplayOrientation(result)
    }

    private fun setPreviewSize(parameters: Camera.Parameters) {
    
        // 获取摄像头支持的宽、高
        val supportedPreviewSizes =
            parameters.supportedPreviewSizes
        var size = supportedPreviewSizes[0]
        Log.d(TAG, "支持 " + size.width + "x" + size.height)
        // 选择一个与设置的差距最小的支持分辨率
        // 10x10 20x20 30x30
        // 12x12
        var m = Math.abs(size.height * size.width - width * height)
        supportedPreviewSizes.removeAt(0)
        val iterator: Iterator<Camera.Size> =
            supportedPreviewSizes.iterator()
        // 遍历
        while (iterator.hasNext()) {
   
            val next = iterator.next()
            Log.d(TAG, "支持 " + next.width + "x" + next.height)
            val n = Math.abs(next.height * next.width - width * height)
            if (n < m) {
   
                m = n
                size = next
            }
        }
        width = size.width
        height = size.height
        parameters.setPreviewSize(width, height)
        Log.d(
            TAG,
            "设置预览分辨率 width:" + size.width + " height:" + size.height
        )
    }

    fun setPreviewDisplay(surfaceHolder: SurfaceHolder) {
   
        this.surfaceHolder = surfaceHolder
        this.surfaceHolder!!.addCallback(this)
    }

    fun setPreviewCallback(previewCallback: PreviewCallback) {
   
        this.previewCallback = previewCallback
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
   }
    override fun surfaceChanged(
        holder: SurfaceHolder,
        format: Int,
        width: Int,
        height: Int
    ) {
   
        // 释放摄像头
        stopPreview()
        // 开启摄像头
        startPreview()
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
   
        stopPreview()
    }

    override fun onPreviewFrame(
        data: ByteArray,
        camera
一款免费的流媒体直播软件,主要用于流媒体直播,视频直播,视频点播,视频录制等应用,与FMS、WOWZA、RED5一道,作为用户流媒体直播应用的可选方案。 1、支持Windows/Linux等多种操作系统; 2、采用开发的流媒体协议,而非私有协议,可以与主流系统对接,至少支持RTMP推流,RTMP拉流,RTSP拉流三种方式获取直播流,支持对获取的直播流再按照TS组播或者单播,RTMP,RTSP,HLS,TS OVER HTTP,FLV OVER HTTP等标准流媒体协议的方式进行对外直播转发; 3、支持录制,可定时录制或手动录制,能录制MP4或者FLV格式文件,录制时支持生成新文件或者追加文件两种方式,支持录制超过4G的文件; 4、单台服务器可支持1000个以上并发用户,直播延时小于2秒; 5、支持RTMP转发功能,能够把AMS上的直播流转发给其它AMS服务器或者第三方的RTMP服务器,例如RED5,FMS,WOWZA流媒体服务器; 6、 支持PC/手机/平板电脑等多终端访问,无论是windows,linux,或者andriod以及IOS系统访问收看直播时都无需下载插件,直接观看; 7、响应点播时间控制在100ms以内,支持暂停、拖动等特技操作; 8、支持文件直播,可把硬盘上存在的FLV文件编目后,按设定好的任务和顺序进行直播; 9、内置Web应用系统,无需复杂配置,可直接部署到用户服务器使用,也可以选配更复杂的媒资管理系统,完成类似优酷土豆的应用模式; 10、开放的系统架构,提供二次开发接口,可轻松的融入到用户已有的平台或网站平台中、或在此基础上进行二次开放等 ------------------------------------------------------------------------- 使用说明: 1、关于安装运行:软件解压到硬盘上后,执行AokuMServiceManager,即可完成AMS服务的安装,安装完成后,在Windows系统服务中会出现一个AokuMService服务; 2、关于端口:AMS默认的管理端口是9001,可通过浏览器登陆http://127.0.0.1:9001/main.html进行管理;AMS默认的RTMP端口是1935,默认的rtsp端口是5554,默认的flv over http端口是7000,默认的ts over http端口是8008,默认的hls端口也是9001; 3、关于推流:使用直播你需要有一个支持rtmp推流的编码器,建议用奥酷全接口高清编码器,当然你也可以通过FME进行软编码来实现; 4、关于接收直播:AMS正常启动并发布上直播流后,你可以在网络内的任一电脑上打开http://ip:9001,通过浏览器观看直播,若开启了hls功能,通过iphone或者ipad登陆http://ip:9001即可观看直播。 5、若开启了TS组播,请确认防火墙是否允许组播,另外您网络内的路由器是否支持组播,接收组播是需要用VLC播放器来完成; 6、接收flv over http也是通过VLC播放器或者支持类似协议的播放器来测试。 7、若使用中出现问题,你可以查看logs目录下的rtmpserver.log文件,里面有详细的错误日志描述。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dora丶Android

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值