FFmpeg和ZLMediaKit 实现本地视频推流

news/2025/10/1 20:11:16/文章来源:https://www.cnblogs.com/jingzh/p/19122795

目录
  • 1 本地视频推流
    • 1.1 简介
      • 1.1.1 FFmpeg
      • 1.1.2 ZLMediaKit
    • 1.2 环境准备
      • 1.2.1 ZLMediaKit 安装配置
      • 1.2.2 FFmpeg 安装(可选)
    • 1.3 整合
      • 1.3.1 pom和配置
      • 1.3.2 推流配置类
      • 1.3.3 推流服务类
      • 1.3.4 前端部分

1 本地视频推流

1.1 简介

FFmpeg 是一套强大的开源音视频处理工具,能够对本地视频进行解码、转码和推流。ZLMediaKit 则是一个轻量级、高效的流媒体服务器,负责接收并分发视频流。
两者结合时,通常由FFmpeg将处理后的视频流,以RTMPRTSP等协议推送至ZLMediaKit服务器,再由ZLMediaKit实现多终端、多协议的实时直播分发。

1.1.1 FFmpeg

FFmpeg 音视频编码、转换、处理的瑞士军刀,是一套可以记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音视频的完整解决方案
主要网址:

  • FFmpeg 官网:https://www.ffmpeg.org
  • FFmpeg 中文网:https://ffmpeg.github.net.cn
  • GitHub: https://github.com/FFmpeg/FFmpeg

特点:

  • 完整的音视频解决方案
  • 先进的编解码库libavcodec
  • 跨平台支持

核心工具:FFmpeg主要包含三个强大的命令行工具:

  • ffmpeg:用于音视频转码、转换。例如,将一个AVI文件转换为TS文件并设置视频码率:ffmpeg -i input.avi -b:v 640k output.ts
  • ffplay:一个简单的媒体播放器,可以用来快速播放音视频文件。例如:ffplay test.avi
  • ffprobe:用于查看多媒体文件的信息,比如格式、编码、流详情等。

1.1.2 ZLMediaKit

ZLMediaKit:运营级流媒体服务器与服务框架,基于C++11的高性能运营级流媒体服务框架主要网址:

  • GitHub: https://github.com/ZLMediaKit/ZLMediaKit
  • Gitee(国内镜像): https://gitee.com/xia-chu/ZLMediaKit

特点:

  • 基于C++11,高性能
  • 支持多种流媒体协议及互转
  • 支持全平台,低延迟
  • 核心定位:它是一个流媒体服务器,负责接收、分发和转换各种格式的媒体流。
  • 协议支持:非常全面,支持RTSPRTMPHLSHTTP-FLVWebSocket-FLVWebRTC等主流协议,并能在这些协议间互相转换。

1.2 环境准备

1.2.1 ZLMediaKit 安装配置

下载安装

# 拉取镜像
docker pull zlmediakit/zlmediakit:master# 启动
docker run -d \--name zlm-server \-p 1935:1935 \-p 8080:80 \-p 8554:554 \-p 10000:10000 \-p 10000:10000/udp \-p 8000:8000/udp \-v /docker-volumes/zlmediakit/conf/config.ini:/opt/media/conf/config.ini \zlmediakit/zlmediakit:master

配置文件 (config.ini)

[hls]
broadcastRecordTs=0
deleteDelaySec=600    # 推流的视频保存多久(10分钟)
fileBufSize=65536
filePath=./www    # 保存路径
segDur=2    # 单个.ts 切片时长(秒)。
segNum=1000  # 直播时.m3u8 里最多同时保留多少个切片。
segRetain=9999    # 磁盘上实际保留多少个历史切片

启动服务

# 查看启动状态
docker logs -f zlm-server

1.2.2 FFmpeg 安装(可选)

下载路径:https://www.gyan.dev/ffmpeg/builds/
选择正式版的ffmpeg,这两个都可以选
30ee59d45483009ea08176bdc261f19d_cfdf7774e64842c3a1f40c487e17d5c8

配置环境变量找到 bin 目录,将其配到 path 环境变量中,执行 ffmpeg --version 验证
与java集合方面:

  • Jaffree:需要本地/服务器已经有 ffmpeg 命令(需要配置路径)。
  • bytedeco/javacpp-presets:依赖里自带 FFmpeg 原生库(无需单独安装 ffmpeg)。
  • Commons Exec / ProcessBuilder:要安装 ffmpeg

1.3 整合

1.3.1 pom和配置

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-exec</artifactId><version>1.3</version>
</dependency><!-- jaffree 需要的 -->
<dependency><groupId>com.github.kokorin.jaffree</groupId><artifactId>jaffree</artifactId><version>0.11.0</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version> <!-- 版本可调整 -->
</dependency>
<!-- bytedeco -->
<dependency><groupId>org.bytedeco</groupId><artifactId>ffmpeg-platform</artifactId><version>6.0-1.5.10</version> <!-- 版本号示例,请检查最新 -->
</dependency>

配置

# 文件上传配置
spring:servlet:multipart:max-file-size: 1GBmax-request-size: 1GBstream:zlm-host: 127.0.0.1rtmp-port: 1935http-port: 8099ffmpeg-path: ffmpegvideo-path: \videos\      

1.3.2 推流配置类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "stream")
public class StreamConfig {/*** ZLMediaKit服务地址*/private String zlmHost;/*** RTMP推流端口*/private Integer rtmpPort;/*** HTTP-FLV拉流端口*/private Integer httpPort;/*** FFmpeg可执行文件路径*/private String ffmpegPath;/*** 视频存储路径*/private String videoPath;}

1.3.3 推流服务类

import com.lyk.plugflow.config.StreamConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Slf4j
@Service
public class StreamService {@Autowiredprivate StreamConfig streamConfig;// 存储推流进程private final Map<String, DefaultExecutor> streamProcesses = new ConcurrentHashMap<>();// 添加手动停止标记private final Map<String, Boolean> manualStopFlags = new ConcurrentHashMap<>();/*** 开始推流*/public boolean startStream(String videoPath, String streamKey) {try {// 检查视频文件是否存在File videoFile = new File(videoPath);if (!videoFile.exists()) {log.error("视频文件不存在: {}", videoPath);return false;}// 构建RTMP推流地址String rtmpUrl = String.format("rtmp://%s:%d/live/%s",streamConfig.getZlmHost(), streamConfig.getRtmpPort(), streamKey);// 构建FFmpeg命令CommandLine cmdLine = getCommandLine(videoPath, rtmpUrl);// 创建执行器DefaultExecutor executor = new DefaultExecutor();executor.setExitValue(0);// 设置watchdog用于进程管理ExecuteWatchdog watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT);executor.setWatchdog(watchdog);// 设置输出流ByteArrayOutputStream outputStream = new ByteArrayOutputStream();PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);executor.setStreamHandler(streamHandler);// 异步执行executor.execute(cmdLine, new ExecuteResultHandler() {@Overridepublic void onProcessComplete(int exitValue) {log.info("推流完成, streamKey: {}, exitValue: {}", streamKey, exitValue);streamProcesses.remove(streamKey);}@Overridepublic void onProcessFailed(ExecuteException e) {boolean isManualStop = manualStopFlags.remove(streamKey);if (isManualStop) {log.info("推流已手动停止, streamKey: {}", streamKey);} else {log.error("推流失败, streamKey: {}, error: {}", streamKey, e.getMessage());}streamProcesses.remove(streamKey);}});// 保存进程引用streamProcesses.put(streamKey, executor);log.info("开始推流, streamKey: {}, rtmpUrl: {}", streamKey, rtmpUrl);return true;} catch (Exception e) {log.error("推流启动失败", e);return false;}}//此处是用 直接 runtime 方式 构建也可以用 Jaffree 方式private CommandLine getCommandLine(String videoPath, String rtmpUrl) {CommandLine cmdLine = new CommandLine(streamConfig.getFfmpegPath());cmdLine.addArgument("-re"); // 按原始帧率读取cmdLine.addArgument("-i");cmdLine.addArgument(videoPath);cmdLine.addArgument("-c:v");cmdLine.addArgument("libx264"); // 视频编码cmdLine.addArgument("-c:a");cmdLine.addArgument("aac"); // 音频编码cmdLine.addArgument("-f");cmdLine.addArgument("flv"); // 输出格式cmdLine.addArgument("-flvflags");cmdLine.addArgument("no_duration_filesize");cmdLine.addArgument(rtmpUrl);return cmdLine;}/*** 停止推流*/public boolean stopStream(String streamKey) {try {DefaultExecutor executor = streamProcesses.get(streamKey);if (executor != null) {// 设置手动停止标记manualStopFlags.put(streamKey, true);ExecuteWatchdog watchdog = executor.getWatchdog();if (watchdog != null) {watchdog.destroyProcess();} else {log.warn("进程没有watchdog,无法强制终止, streamKey: {}", streamKey);}streamProcesses.remove(streamKey);log.info("停止推流成功, streamKey: {}", streamKey);return true;}return false;} catch (Exception e) {log.error("停止推流失败", e);return false;}}/*** 获取拉流地址*/public String getPlayUrl(String streamKey, String protocol) {return switch (protocol.toLowerCase()) {case "flv" -> String.format("http://%s:%d/live/%s.live.flv",streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);case "hls" -> String.format("http://%s:%d/live/%s/hls.m3u8",streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);default -> null;};}/*** 检查推流状态*/public boolean isStreaming(String streamKey) {return streamProcesses.containsKey(streamKey);}
}

1.3.4 前端部分

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>FLV直播播放器</title><style>body {margin: 0;padding: 20px;font-family: Arial, sans-serif;background-color: #f0f0f0;}.player-container {max-width: 800px;margin: 0 auto;background: white;border-radius: 8px;padding: 20px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);}#videoElement {width: 100%;height: 450px;background-color: #000;border-radius: 4px;}.controls {margin-top: 15px;text-align: center;}button {padding: 10px 20px;margin: 0 5px;border: none;border-radius: 4px;background-color: #007bff;color: white;cursor: pointer;font-size: 14px;}button:hover {background-color: #0056b3;}button:disabled {background-color: #ccc;cursor: not-allowed;}.status {margin-top: 10px;padding: 10px;border-radius: 4px;text-align: center;}.status.success {background-color: #d4edda;color: #155724;}.status.error {background-color: #f8d7da;color: #721c24;}.status.info {background-color: #d1ecf1;color: #0c5460;}</style></head><body><div class="player-container"><h1>FLV直播播放器</h1><video id="videoElement" controls muted>您的浏览器不支持视频播放</video><div class="controls"><button id="playBtn">播放</button><button id="pauseBtn" disabled>暂停</button><button id="stopBtn" disabled>停止</button><button id="muteBtn">静音</button></div><div id="status" class="status info">准备就绪,点击播放开始观看直播</div></div><!-- 使用flv.js库 --><script src="https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js"></script><script>let flvPlayer = null;const videoElement = document.getElementById('videoElement');const playBtn = document.getElementById('playBtn');const pauseBtn = document.getElementById('pauseBtn');const stopBtn = document.getElementById('stopBtn');const muteBtn = document.getElementById('muteBtn');const statusDiv = document.getElementById('status');// 你的流地址const streamUrl = 'http://127.0.0.1:8099/live/stream.live.flv';function updateStatus(message, type) {statusDiv.textContent = message;statusDiv.className = `status ${type}`;console.log(`[${type.toUpperCase()}] ${message}`);}function updateButtons(playEnabled, pauseEnabled, stopEnabled) {playBtn.disabled = !playEnabled;pauseBtn.disabled = !pauseEnabled;stopBtn.disabled = !stopEnabled;}// 检查浏览器支持if (!flvjs.isSupported()) {updateStatus('您的浏览器不支持FLV播放,请使用Chrome、Firefox或Edge浏览器', 'error');playBtn.disabled = true;}// 播放功能playBtn.addEventListener('click', function () {try {if (flvPlayer) {flvPlayer.destroy();}// 创建FLV播放器flvPlayer = flvjs.createPlayer({type: 'flv',url: streamUrl,isLive: true}, {enableWorker: false,lazyLoad: true,lazyLoadMaxDuration: 3 * 60,deferLoadAfterSourceOpen: false,autoCleanupSourceBuffer: true,enableStashBuffer: false});flvPlayer.attachMediaElement(videoElement);flvPlayer.load();// 监听事件flvPlayer.on(flvjs.Events.ERROR, function (errorType, errorDetail, errorInfo) {console.error('FLV播放器错误:', errorType, errorDetail, errorInfo);updateStatus(`播放错误: ${errorDetail}`, 'error');});flvPlayer.on(flvjs.Events.LOADING_COMPLETE, function () {updateStatus('流加载完成', 'success');});flvPlayer.on(flvjs.Events.RECOVERED_EARLY_EOF, function () {updateStatus('从早期EOF恢复', 'info');});// 开始播放videoElement.play().then(() => {updateStatus('正在播放直播流', 'success');updateButtons(false, true, true);}).catch(error => {console.error('播放失败:', error);updateStatus('播放失败: ' + error.message, 'error');});} catch (error) {console.error('创建播放器失败:', error);updateStatus('创建播放器失败: ' + error.message, 'error');}});// 暂停功能pauseBtn.addEventListener('click', function () {if (videoElement && !videoElement.paused) {videoElement.pause();updateStatus('播放已暂停', 'info');updateButtons(true, false, true);}});// 停止功能stopBtn.addEventListener('click', function () {if (flvPlayer) {flvPlayer.pause();flvPlayer.unload();flvPlayer.destroy();flvPlayer = null;}videoElement.src = '';videoElement.load();updateStatus('播放已停止', 'info');updateButtons(true, false, false);});// 静音功能muteBtn.addEventListener('click', function () {videoElement.muted = !videoElement.muted;muteBtn.textContent = videoElement.muted ? '取消静音' : '静音';updateStatus(videoElement.muted ? '已静音' : '已取消静音', 'info');});// 视频事件监听videoElement.addEventListener('loadstart', function () {updateStatus('开始加载视频流...', 'info');});videoElement.addEventListener('canplay', function () {updateStatus('视频流已准备就绪', 'success');});videoElement.addEventListener('playing', function () {updateStatus('正在播放直播流', 'success');updateButtons(false, true, true);});videoElement.addEventListener('pause', function () {updateStatus('播放已暂停', 'info');updateButtons(true, false, true);});videoElement.addEventListener('error', function (e) {updateStatus('视频播放出错', 'error');updateButtons(true, false, false);});</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/924233.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL 多表查询速查:JOIN、子查询一文全掌握 - 详解

SQL 多表查询速查:JOIN、子查询一文全掌握 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

MySQL复合查询(重点) - 详解

MySQL复合查询(重点) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

网站备案期间可以建站荣欣建设集团有限公司网站

目录 1.应用场景2.类似的数据同步工具3.DataX 与 Canal 有什么区别 DataX是阿里巴巴开源的一款数据同步工具&#xff0c;使用Java语言开发的。它提供了从各类数据源读取数据以及向各类数据源写入数据的功能&#xff0c;支持包括MySQL、Oracle、SQLServer、PostgreSQL、HDFS、HB…

14.单臂路由(2025年9月29日) - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

AtCoder Beginner Contest 424 425 部分题解

ABC424E注意到,若两根木棍长度相同,那么它们被操作的顺序也是相邻的。于是,若两根木棍“来源”相同,那么它们始终在同一“批次”中被操作。例如:\([1] \to [1/2, 1/2] \to [1/4, 1/4, 1/2] \to [1/4, 1/4, 1/4, 1…

网站怎么识别PC 手机装修设计灵感网站

文章目录 串的模式匹配 考纲内容 复习提示 1.简单的模式匹配算法 知识回顾 2.串的模式匹配算法——KMP算法 2.1字符串的前缀、后缀和部分匹配值 2.2KMP算法的原理是什么 3.KMP算法的进一步优化 串的模式匹配 考纲内容 字符串模式匹配 复习提示 本章是统考大纲第6章内…

【C++】Visual Studio+CMake 开发 C++ 入门指南:从环境搭建到项目实战 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

学习:uniapp全栈微信小程序vue3后台-额外/精彩报错篇 - 教程

学习:uniapp全栈微信小程序vue3后台-额外/精彩报错篇 - 教程2025-10-01 20:04 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !import…

织梦配置手机网站手机企业网站怎么做

0x01 初识NTLM协议 基本概念&#xff1a;NTLM(NT LAN Manager)认证是一种早期的Windows网络身份认证协议。它在Windows系统中用于验证用户的身份&#xff0c;并提供对网络资源的访问控制&#xff0c;它是一种基于Challenge/Response的认证机制。 认证流程 NTLM协议Challenge…

关于滚动数组

i & 1 还是太丑陋了,可以设一个 ind,每次 i++ 时 ind = 1 - ind。 i + 1 就是 1 - ind。

第九篇

今天是10月1日,ok也是终于等到了国庆节放假,不容易啊,坐了十几个小时的火车终于是顺利到家了。

2025 年宁波搬家公司推荐 TOP 权威榜单发布,多维度解读宁波搬家服务公司创新亮点举措

引言 在宁波这座不断发展的城市,搬家服务需求日益增长,但行业内却存在不少亟待解决的问题。部分小型搬家团队缺乏规范管理,服务质量参差不齐,常常出现物品损坏后推诿责任、坐地起价等现象,让消费者蒙受损失。有的…

2025 年检测器厂家推荐 TOP 品牌权威排名,防爆火焰 / 一体化火焰 / 紫外线火焰 / 离子火焰 / 红外线火焰 / 红紫外复合火焰 / 智能火焰检测器公司推荐

引言 在工业生产、能源供应、环境保护等众多领域,检测器作为关键的监测设备,其性能与质量直接关系到生产安全、效率及数据准确性。当前,检测器市场品牌众多,产品种类繁杂,技术水平参差不齐。部分厂家为追求短期利…

【Git】Git 操作指令大全及运用场景详解

【Git】Git 操作指令大全及运用场景详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

网站建设的目的和作用网店推广软文范例

摘要&#xff1a;本文整理自字节跳动基础架构工程师李国君&#xff0c;在 Streaming Lakehouse Meetup 的分享。幸福里业务是一种典型的交易、事务类型的业务场景&#xff0c;这种业务场景在实时数仓建模中遇到了诸多挑战。本次分享主要介绍幸福里业务基于 Flink & Paimon …

9-29

(1)今天预习了java的课程 (2)明天继续深造

10-1

(1)今天预习了java的课程 (2)明天继续深造

百度如何提交网站做网站建设推荐

概述 一种开源跨平台的序列化结构化数据的协议。可用于存储数据或在网络上进行数据通信。它提供了用于描述数据结构的接口描述语言&#xff08;IDL&#xff09;&#xff0c;也提供了根据 IDL 产生代码的程序工具。Protocol Buffers的设计目标是简单和性能&#xff0c;所以与 XM…

网上商城网站建设体会青海百度关键词seo

虚拟现实&#xff08;VR&#xff09;技术的出现为我们提供了一种全新的在线教学方式。由广州华锐视点开发的VR线上教学资源平台&#xff0c;作为一个综合性的学习工具&#xff0c;正在教育领域迅速发展&#xff0c;并被越来越多的教育机构和学生所接受。那么&#xff0c;VR线上…

做网站需要几个服务器wordpress网站域名服务器

技术背景 随着智慧数字人、AI数字人的兴起&#xff0c;越来越多的公司着手构建​全息、真实感数字角色等技术合成的数字仿真人虚拟形象&#xff0c;通过“虚拟形象语音交互&#xff08;T-T-S、ASR&#xff09;自然语言理解&#xff08;NLU&#xff09;深度学习”&#xff0c;构…