搭建Docker+SRS服务器实现推流拉流的效果

2023-11-05

最初的一个想法,是针对当前的网络电视去的,很多网络电视买回家,还要充很多会员,甚至跌入连环坑。我想给妈妈买一台电视,想把我自己收集的电影电视剧做成一个影视库,通过搭建家庭影院服务器,然后在安卓终端上面点播。最初想得很简单,就是做一个文件服务器就可以了,但是安卓支持的解码器有限,就想着在服务器把各种格式的电影转换成流媒体,推向流媒体服务器。安卓软件直接从流媒体服务器拉流播放就可以了,不考虑解码的问题。

之前写过一个手机直播的模型,使用的rtmp服务器是nginx,这次我使用的是用Docker搭建的SRS服务器。

关于使用Docker搭建SRS服务器可以参照官网的文章:

http://ossrs.net/lts/zh-cn/docs/v4/doc/getting-started

首先我在一个虚拟机上面拉取镜像

docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:4

然后运行一个容器

docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 \
    registry.cn-hangzhou.aliyuncs.com/ossrs/srs:4 ./objs/srs -c conf/docker.conf

这是官网的指令,我一个字没改过。

就这样,一个SRS服务器就建好了。我的服务器host映射为srs.chris.com。下来就是推流了。

推流之前,要准备好一两段视频。还要在本地安装ffmpeg。安装方法参照:

https://blog.csdn.net/weixin_45947430/article/details/122509083

我是用的windows11-21h2,安装过后始终是找不到命令,反复确认了多次,也不知道是啥问题。没办法,只好使用绝对路径。

第一种推流,用命令行:

ffmpeg -re -stream_loop -1 -i ./v2.mp4 -c copy -f flv rtmp://srs.chris.com/live/livestream

因为视频比较短,所以添加了-stream -1进行循环推流。

srs提供了一个播放终端,在浏览器打开http://srs.chris.con:8080就可以打开页面,找到srs播放器,就能直接播放了。

 不过也可以下载一个vlc播放器,来打开一个串行流。

 这样基本推拉流都可以实现了。

我的最终目标是用Java写一个服务,所以最终需要使用Java代码来推流。

Java代码推流有两种方式。

第一种,在在Java代码中执行ffmpeg命令。

    @SneakyThrows
    public static void srsFile(String streamName,String filePath) {
            String command = "G:\\downs\\ffmpeg\\bin\\ffmpeg -re -stream_loop -1 " +
                    "-i " +
                    filePath +
                    " -c copy " +
                    "-f flv rtmp://srs.chris.com/live/" + streamName;
            System.out.println(command);
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println("视频推流信息[" + line + "]");
        }
    }

第二种,是使用javacv库,通过抓取视频文件的帧,逐帧推送。

package com.chris.demo;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;

/**
 * @author willzhao
 * @version 1.0
 * @description 读取指定的mp4文件,推送到SRS服务器
 * @date 2021/11/19 8:49
 */
@Slf4j
public class PushMp4 {
    /**
     * 本地MP4文件的完整路径(两分零五秒的视频)
     */
    private static final String MP4_FILE_PATH = "G:\\downs\\video\\v2.mp4";

    /**
     * SRS的推流地址
     */
    private static final String SRS_PUSH_ADDRESS = "rtmp://192.168.2.61/live/livestream";

    /**
     * 读取指定的mp4文件,推送到SRS服务器
     *
     * @param sourceFilePath 视频文件的绝对路径
     * @param PUSH_ADDRESS   推流地址
     * @throws Exception
     */
    public static void grabAndPush(String sourceFilePath, String PUSH_ADDRESS) throws Exception {
        // ffmepg日志级别
        avutil.av_log_set_level(avutil.AV_LOG_ERROR);
        FFmpegLogCallback.set();

        // 实例化帧抓取器对象,将文件路径传入
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(MP4_FILE_PATH);

        long startTime = System.currentTimeMillis();
        log.info("开始初始化帧抓取器");

        // 初始化帧抓取器,例如数据结构(时间戳、编码器上下文、帧对象等),
        // 如果入参等于true,还会调用avformat_find_stream_info方法获取流的信息,放入AVFormatContext类型的成员变量oc中
        grabber.start(true);

        log.info("帧抓取器初始化完成,耗时[{}]毫秒", System.currentTimeMillis() - startTime);

        // grabber.start方法中,初始化的解码器信息存在放在grabber的成员变量oc中
        AVFormatContext avFormatContext = grabber.getFormatContext();

        // 文件内有几个媒体流(一般是视频流+音频流)
        int streamNum = avFormatContext.nb_streams();

        // 没有媒体流就不用继续了
        if (streamNum < 1) {
            log.error("文件内不存在媒体流");
            return;
        }

        // 取得视频的帧率
        int frameRate = (int) grabber.getVideoFrameRate();

        log.info("视频帧率[{}],视频时长[{}]秒,媒体流数量[{}]",
                frameRate,
                avFormatContext.duration() / 1000000,
                avFormatContext.nb_streams());

        // 遍历每一个流,检查其类型
        for (int i = 0; i < streamNum; i++) {
            AVStream avStream = avFormatContext.streams(i);
            AVCodecParameters avCodecParameters = avStream.codecpar();
            log.info("流的索引[{}],编码器类型[{}],编码器ID[{}]", i, avCodecParameters.codec_type(), avCodecParameters.codec_id());
        }

        // 视频宽度
        int frameWidth = grabber.getImageWidth();
        // 视频高度
        int frameHeight = grabber.getImageHeight();
        // 音频通道数量
        int audioChannels = grabber.getAudioChannels();

        log.info("视频宽度[{}],视频高度[{}],音频通道数[{}]",
                frameWidth,
                frameHeight,
                audioChannels);

        // 实例化FFmpegFrameRecorder,将SRS的推送地址传入
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(SRS_PUSH_ADDRESS,
                320,
                (int) (frameHeight / (frameWidth * 1.0) * 320),
                audioChannels);

        // 设置编码格式
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);

        // 设置封装格式
        recorder.setFormat("flv");

        // 一秒内的帧数
        recorder.setFrameRate(frameRate);

        // 两个关键帧之间的帧数
        recorder.setGopSize(frameRate);

        // 设置音频通道数,与视频源的通道数相等
        recorder.setAudioChannels(grabber.getAudioChannels());

        startTime = System.currentTimeMillis();
        log.info("开始初始化帧抓取器");

        // 初始化帧录制器,例如数据结构(音频流、视频流指针,编码器),
        // 调用av_guess_format方法,确定视频输出时的封装方式,
        // 媒体上下文对象的内存分配,
        // 编码器的各项参数设置
        recorder.start();

        log.info("帧录制初始化完成,耗时[{}]毫秒", System.currentTimeMillis() - startTime);

        Frame frame;

        startTime = System.currentTimeMillis();

        log.info("开始推流");

        long videoTS = 0;

        int videoFrameNum = 0;
        int audioFrameNum = 0;
        int dataFrameNum = 0;

        // 假设一秒钟15帧,那么两帧间隔就是(1000/15)毫秒
        int interVal = 1000 / frameRate;
        // 发送完一帧后sleep的时间,不能完全等于(1000/frameRate),不然会卡顿,
        // 要更小一些,这里取八分之一
        interVal /= 8;

        // 持续从视频源取帧
        while (null != (frame = grabber.grab())) {
            videoTS = 1000 * (System.currentTimeMillis() - startTime);

            // 时间戳
            recorder.setTimestamp(videoTS);

            // 有图像,就把视频帧加一
            if (null != frame.image) {
                videoFrameNum++;
            }

            // 有声音,就把音频帧加一
            if (null != frame.samples) {
                audioFrameNum++;
            }

            // 有数据,就把数据帧加一
            if (null != frame.data) {
                dataFrameNum++;
            }

            // 取出的每一帧,都推送到SRS
            recorder.record(frame);

            // 停顿一下再推送
            Thread.sleep(interVal);
        }

        log.info("推送完成,视频帧[{}],音频帧[{}],数据帧[{}],耗时[{}]秒",
                videoFrameNum,
                audioFrameNum,
                dataFrameNum,
                (System.currentTimeMillis() - startTime) / 1000);

        // 关闭帧录制器
        recorder.close();
        // 关闭帧抓取器
        grabber.close();
    }

    public static void main(String[] args) throws Exception {
        grabAndPush(MP4_FILE_PATH, SRS_PUSH_ADDRESS);
    }
}

上面这是一段测试成功的代码,也没有优化。看起来要复杂一些,但是可能更灵活。不过目前没有仔细研究,拉流效果感觉有些问题,也没有找到循环推流的尝试。

不过我想如果要实现我最初的想法,第一要多个视频无缝衔接,因为一个视频推流结束之后这个流 就结束了。后面要连续播放就只能重新打开。还有一个问题就是这种模式应该属于直播模式,如果我想快进跳跃式观影不知道有没有办法实现。还得继续研究。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

搭建Docker+SRS服务器实现推流拉流的效果 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Grails 3.x bootRun 失败

    我正在尝试在 grails 3 1 11 中运行一个项目 但出现错误 失败 构建失败并出现异常 什么地方出了错 任务 bootRun 执行失败 进程 命令 C Program Files Java jdk1 8 0 111 bin java
  • 在 Java 中连接和使用 Cassandra

    我已经阅读了一些关于 Cassandra 是什么以及它可以做什么的教程 但我的问题是如何在 Java 中与 Cassandra 交互 教程会很好 如果可能的话 有人可以告诉我是否应该使用 Thrift 还是 Hector 哪一个更好以及为什
  • Java Swing:从 JOptionPane 获取文本值

    我想创建一个用于 POS 系统的新窗口 用户输入的是客户拥有的金额 并且窗口必须显示兑换金额 我是新来的JOptionPane功能 我一直在使用JAVAFX并且它是不同的 这是我的代码 public static void main Str
  • Java EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • Java按日期升序对列表对象进行排序[重复]

    这个问题在这里已经有答案了 我想按一个参数对对象列表进行排序 其日期格式为 YYYY MM DD HH mm 按升序排列 我找不到正确的解决方案 在 python 中使用 lambda 很容易对其进行排序 但在 Java 中我遇到了问题 f
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • 如何在控制器、服务和存储库模式中使用 DTO

    我正在遵循控制器 服务和存储库模式 我只是想知道 DTO 在哪里出现 控制器应该只接收 DTO 吗 我的理解是您不希望外界了解底层域模型 从领域模型到 DTO 的转换应该发生在控制器层还是服务层 在今天使用 Spring MVC 和交互式
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • simpleframework,将空元素反序列化为空字符串而不是 null

    我使用简单框架 http simple sourceforge net http simple sourceforge net 在一个项目中满足我的序列化 反序列化需求 但在处理空 空字符串值时它不能按预期工作 好吧 至少不是我所期望的 如
  • 静态变量的线程安全

    class ABC implements Runnable private static int a private static int b public void run 我有一个如上所述的 Java 类 我有这个类的多个线程 在里面r
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐

  • 高亮必填字段

    程序需求 必填字段高亮提示 为输入文本框添加一个高亮样式 input ng invalid background color rgb 255 255 51 input ng invalid required background color
  • 2023年第五届人工智能与机器学习国际会议(FAIML 2023)

    2023年第五届人工智能与机器学习国际会议 FAIML 2023 重要信息 会议网址 www faiml org 会议时间 2023年4月14 16日 召开地点 中国北京 截稿时间 2023年3月15日 录用通知 投稿后2周内 收录检索 E
  • 高德地图android sdk 地图显示和定位 基本使用方法

    首先 定位和地图是分开的 定位有定位的sdk 地图有地图的sdk 地图显示分为MapView和 AMap 两个类 MapView重写activity内的基本所有生命周期方法 Amap由mapview产生 aMap mapView getMa
  • 剑指Offer 04. 二维数组中的查找

    原题链接 思路 题目中说 每一行都是 从左向右递增的 在一个递增的序列中 查找某个数是否是存在的 二分即可 注意对边界进行判断 时间复杂度 O nlogn 代码 class Solution public boolean check int
  • 将windows按键修改成mac的快捷键

    下载工具autoHotKey AutoHotkey 使用autoHotKey 下载后创建一个脚本 ahk文件 将下面的进行粘贴 右键run script运行当前的脚本 ahk文件 就可以实现mac的快捷键了 a Send a 将 Alt A
  • Visual Studio build tools 安装出错的一种解决办法

    一般是安装包丢失或损坏 那么我么可以用离线下载的方式来先行下载 用 h 看下帮助 主要是Layout参数 下载完 到下载目录安装吧 转载于 https www cnblogs com jackadam p 8279199 html
  • 数据库系统 复习总结

    绪论 关系数据库 关系模型 完整性约束 关系运算 SQL语句 数据库安全 数据库安全性 数据库完整性 数据库设计 数据库设计概述与需求分析 函数依赖 无损连接验证算法 范式 关系模式分解 物理存储结构 磁盘容错技术 主索引 辅助索引 事务
  • 桥接模式-

    定义 也称为桥梁模式 接口模式或柄体模式 是将抽象部分与它的具体实现部分分离 使它们都可以独立地变化 适用场景 1 在抽象和具体实现之间需要增加更多的灵活性的场景 2 一个类存在两个 或多个 独立变化的维度 而这两个 或多个 维度都需要独立
  • 记一次Connection refused: no further information: localhost/127.0.0.1:6379的排错

    解决Redis connection refusd遇到的一个问题 错误描述 我做一个登陆界面时 把token令牌储存在Redis中 输入正确的账号密码时 在控制台显示如下错误 2020 01 31 14 20 34 DEBUG Connec
  • 2023年“网络安全”赛项浙江省金华市选拔赛 任务书

    2023年 网络安全 赛项浙江省金华市选拔赛 任务书 任务书 一 竞赛时间 共计3小时 二 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 Windows操作系统渗透测试 任务二 Linux操作
  • JavaScript基础Day05:对象

    JavaScript基础Day05 对象 文章目录 JavaScript基础Day05 对象 一 对象 1 创建对象 2 自定义构造函数创建对象 3 通过字面量创建对象 4 new关键字 5 this关键字 二 对象的使用 1 获取属性 两
  • [工业互联-5]:工业无线互联总线之IO-Link Wireless

    目录 第1章 IO Link Wireless概述 1 1 IO Link有线 1 2 IO Llink无线出现 1 3 IO Link Wireless概述 1 4 IO Link Wireless性能指标 第2章 IO Link Wir
  • qt android 开发篇之如何实现圆盘(hsv)颜色选择器

    在很多情况 我们开发应用的时候经常会用到颜色选择器 大一的时候我做一个涂鸦软件的时候遇到的一个问题 就是如何在qt上做一个圆盘的颜色选择器 这次做一个例子来让大家了解这种控件是怎么做的 首先我们要理解颜色中的hsv的概念 大家可以直接百度h
  • 对话框--QDialog(自定义对话框简单实例)

    dialog h头文件 ifndef DIALOG H define DIALOG H include
  • 【T+】畅捷通T+修改数据精度小数点,提示小数位数只能改大。

    问题描述 使用畅捷通T 软件过程中 修改数据精度过程中 由于误操作 误将数量小数位数改大 并且保存了 但是看软件的说明以及提示 软件提示 数量小数位只能改大 软件说明 建账后位数只能改长不能改短 解决方法 首先 表明自己的立场 如果小数位不
  • 什么是 C# ,什么是 .Net 框架(.Net Framework)及其开发环境(学习心得 1)

    超级小白友好 讲解C 基础 每集5分钟轻松学习 拒绝从入门到放弃 C 是 Net 框架的一部分 用于编写 Net 应用程序 文章目录 Net 框架 Net Framework C 的集成开发环境 Integrated Development
  • ESP32(MicroPython)四轮差速底盘遥控

    本项目主控改为ESP32 C3 沿用之前的L298N电机驱动 12 6v 18650电池组 LM7805降压模块的方案 电机改用1 19减速比的 使用130mm车轮 主要考虑越野用途 遥控方面 本项目使用HC 14模块 实测连接到电脑可以通
  • 大数据技术与实践学习笔记(1 of 3,from hitwh)

    大数据技术与实践 注意 由于文章图片是通过typora一键上传图片实现 该功能还存在bug 容易导致图片顺序混乱 文章开头提供了原版文章的 pdf 资源下载 推荐下载 pdf 后观看 文章目录 大数据技术与实践 1 1 大数据技术概述 一
  • CSDN文章复制没有图片只有文字

    有朋友反映 复制CSDN页面内容 然后粘贴 结果发现图片没拷过来 只有文字部分 是这样吗 做一个测试 确认这个问题存在 随便找了一个页面 53 岁的我都退休了 但好想跨行当一名程序员 结果收到了 3 份 Offer 相当的励志 人家53岁老
  • 搭建Docker+SRS服务器实现推流拉流的效果

    最初的一个想法 是针对当前的网络电视去的 很多网络电视买回家 还要充很多会员 甚至跌入连环坑 我想给妈妈买一台电视 想把我自己收集的电影电视剧做成一个影视库 通过搭建家庭影院服务器 然后在安卓终端上面点播 最初想得很简单 就是做一个文件服务