Compare commits

...

273 Commits

Author SHA1 Message Date
langhuihui
84d9e05409 1、修复engine打印流程超时日志序列化错误的问题
2、修复monitor插件订阅时引起的流的事件循环阻塞问题
3、修复webrtc插件iceserver配置未生效的问题
4、rtsp插件删除协议自动切换逻辑,改为第三方库自动切换
5、pull配置新增代理配置,对hdl和hls插件有效
6、hls插件增加转发模式配置,实现纯转发功能
2023-04-15 08:54:35 +08:00
dexter
248eefd1b0 1 修复热更新api导致的崩溃问题
2 兼容ts中streamID不在标准中定义的情况
   3 修复按需拉流中无音频时订阅者等待的问题
   4 gb插件修复收到已连接的设备的注册包的情况
   5 record插件flv头中添加meta信息
2023-04-09 12:54:49 +08:00
dexter
c95dbfb291 1、新增monitor插件,用于将监控数据写入文件中,方便后续分析
远程控制台已增加监控数据查看功能
2、增加日志多语言机制
3、engine中增加日志语言配置,默认使用zh即中文
4、engine中增加心跳时间广播,并增加心跳事件间隔配置,默认为5秒
2023-04-04 20:58:07 +08:00
dexter
e65da00dac 将常见问题加入注释 2023-03-28 20:48:19 +08:00
dexter
5a436e0965 1、修复上一个版本出现的订阅者限速失效的 bug(影响秒开)
2、修复 GB28181 接收 TCP 流缓冲为 0 的错误
3、GB28181 现在默认接收 TCP 流
4、GB28181 增加监听网卡地址的配置
5、GB28181 增加同时多路 dump 的功能
6、修复rtmp、rtsp续推后时间戳计算
7、优化 rtsp 推流如果没有音频,订阅者将不再等待音频,实现秒开
8、控制台输出链接信息,常见问题已放在入口文件的注释中。
2023-03-28 20:06:45 +08:00
dexter
4652af0975 1、通过提高时间戳精度修复rtsp推流时间戳精度丢失导致的播放卡问题
2、修复rtsp推流后,webrtc无法播放问题
3、修复由于rtp包组包逻辑问题导致rtsp拉流播放卡顿问题
4、hls插件增加http配置项,可独立设置端口
2023-03-23 09:43:27 +08:00
dexter
ba0c2fbf8e 修复avcc写入时间戳的bug,webrtc插件支持tcp连接方式 2023-03-14 09:37:54 +08:00
dexter
0c714cc258 GB插件引用的rtp从v2改回v1,engine做相应修改
对room和webrtc做了一些修改,针对room的demo进行优化
对preview插件的流列表增加了类型显示
2023-03-13 13:46:37 +08:00
dexter
d9933f0c65 readme改成英文 2023-03-10 14:04:16 +08:00
dexter
10aebe3085 1、预览插件增加对按需拉流信息的罗列
2、引入v3版的ps解包逻辑
3、修复非rtmp系的首个绝对时间戳初始化
4、修复订阅时如果无音频时首个视频帧没有被订阅的bug
2023-03-09 19:48:39 +08:00
dexter
56918b5dd4 1、WebRTC订阅AAC流时将AAC通过DataChannel传输
2、Rtmp协议接收releaseStream时不再回复client
3、优化RTSP的AAC数据的解析
4、修复RTSP出错写日志时Panic的问题
5、engine启动插件循序遵循插件注册顺序
2023-03-04 08:15:15 +08:00
dexter
b9e74c161d 修复gb无法获取通道的问题 2023-03-01 20:14:07 +08:00
dexter
33875ecb3d 修复hls插件panic的问题,和psPublisher的json序列化问题 2023-03-01 10:00:59 +08:00
dexter
eaa39044d4 将ps流接收后发布逻辑移入engine中,frame中新增adts数据 2023-02-28 20:22:00 +08:00
dexter
b01a498688 更新room插件 2023-02-27 18:15:51 +08:00
dexter
dcfb354f8c 1、优化了rtp包写入引擎时的内存复用机制
2、优化了webrtc插件
3、订阅时接收的VideoFrame和AudioFrame增加了track属性方便读取track中的数据
4、优化了push的重连逻辑以及终止重连的判断
5、获取annexb格式时如果是关键帧则自动带上sps等
2023-02-26 11:36:31 +08:00
dexter
52681147d6 修复flv录制时的时间戳,hls写入去除锁 2023-02-22 13:42:11 +08:00
dexter
3cd643fd31 修复webrtc推流错误
webrtc插件增加推流测试页面
HLS插件音视频分离
GB28181插件invite恢复使用通道ID,以及合入PR等
2023-02-21 21:33:13 +08:00
dexter
e6f73e71aa 修复WriteADTS没有写入时间戳的bug
修复webrtc推流时payloadType错误问题
修复GB28181的invite报文格式,以及ps流处理
2023-02-21 08:27:17 +08:00
dexter
9c278864c2 修复MP4录制,gb解析ps重写,rtmp插件握手兼容性 2023-02-20 00:24:06 +08:00
dexter
0b5c36b1c1 1、修复linux下没有生成fatal.log的问题
2、fatal.log现在采用追加方式写入
3、优化HLS(ts文件)内存复用
4、修复rtmp协议在接收推流时时间戳生成错误导致内存泄露等问题
5、修复datatrack没有被广播的bug(引起房间demo无法接收到信息)
2023-02-15 21:21:53 +08:00
dexter
97f7d6a22d 修复WriteTimeout传入时多乘以了Second
修复订阅RTP时无音频是的panic
WriteAVCC传入指针防止意外
2023-02-08 22:38:37 +08:00
dexter
bf18453401 1、引入ClockRate, 用于RTSP的RTP时间戳处理(MPEG2中的时间戳是固定90KHZ)
2、修复控制流Sleep传入的时间单位错误
3、优化TS格式解析内存复用,减少内存分配
4、修复rtmp接续发布功能
5、修复录像插件写入MP4文件时,视频格式错误的问题
6、更新WebTansport的插件的quic-go依赖,用于适配go1.20
2023-02-08 14:26:37 +08:00
dexter
7dc8966d55 1、当发布者离线时没有订阅者时,流的超时时间从1s改为10ms。
2、Track增加离线状态,当发布者离线时,Track状态改为离线状态,当发布者重新上线时,Track状态改为在线状态。
    3、Track在恢复在线后,记录时间戳的差值,保持后续时间戳和之前的连续。
    4、进一步优化订阅者读取音视频同步逻辑。
2023-02-06 14:22:45 +08:00
dexter
9cb7010507 1、增加SErepublish事件表示再次发布成功事件,使得SEPublish事件表示第一次发布成功。
2、修复push时ReConnectCount没有计数增加。
3、使用github.com/quic-go/quic-go v0.32.0版本。
4、修复订阅模式>1时没有从IDR帧开始的问题。
5、RTMP插件修复推流时MessageStreamID没有设置的问题。
6、RTSP插件升级第三方依赖库aler9/gortsplib 到 v2.1.0,并进行适配。
7、Stream中将保存状态变更历史信息。
2023-02-03 21:38:01 +08:00
dexter
8d5c6d6d11 增加时光回溯功能
订阅模式参数
内存回收机制
增加对时间长度的配置
解决首屏渲染无音频问题
修复rtmp对外推拉没有带入参数
修复dts生成时pts超过最大值导致错误的问题
修复rtmp解析时对扩展时间戳的处理
修复几处判空panic问题
2023-02-02 12:46:42 +08:00
dexter
3ead36ccf7 fix: 修复engine一处负优化,增加一个首屏渲染开关 2023-01-22 22:06:08 +08:00
dexter
a4e1f4950d 1.优化Track数据写入流程,调用更清晰自然
2.对Subscribe增加3个配置项,用于指定订阅指定Track时的参数名
3.修复首次写入AbsTime总是从0开始引起的问题
4.修复订阅时,读取音频数据过迟问题
5.修复对外推拉流(rtmp、rtsp)时,异常退出没有关闭远端连接问题
6.修复record和hls收到发布事件后重复订阅问题
2023-01-18 23:28:26 +08:00
dexter
53cc788857 1.将amf0编码器和解码器放到codec/amf.go中
2.对写入avcc音频进行长度判断
3.录制者设置为内部订阅者
4.修复edge插件远程拉取HDL时,初始化错误
5.增加MallocSlice函数用于减少内存复制
6.录制flv写完后对头部进行修改加入关键帧元数据使得播放flv可以拖动进度条
2023-01-16 09:34:09 +08:00
dexter
6b40297c69 1.拉取远端hls流平滑处理,解决卡顿问题。
2.拉流逻辑增加一处判空
3.设置speedlimit默认值从0改为500
4.snap插件的订阅者设置为内部订阅者防止影响自动关闭
5.gb28181的tcp范围端口不再读取ssrc值判断
2023-01-10 09:25:18 +08:00
dexter
f8e50d00f5 注释一处println 2023-01-03 19:34:40 +08:00
dexter
361b0522e1 1.修复Stream序列化循环引用问题
2.实现对subscribe中SubVideoTracks和SubAudioTracks的支持
3.修复读取ts中aac格式数据多次flush问题
4.修复subscribe结束时判断IsClosed调用对象错误
5.增加DefaultYaml功能解决插件默认配置被全局配置覆盖问题
6.修复rtsp插件订阅时参数未传入问题
7.snap插件和record插件采用DefaultYaml默认配置
2023-01-03 19:24:15 +08:00
dexter
27784f0a03 1.修复delayclosetimeout值被覆盖的bug
2.重构流中订阅者集合,减少订阅者对轨道的无效等待
3.实现内部订阅者功能,使得HLS插件不再影响最后一个订阅者离开逻辑
4.破坏性修改:pull配置中pullonstart类型改为map,pullonsubscribe修改为pullonsub,类型改为map
5.rtmp插件增加对远端流rtmps协议的支持,优化握手过程
6.修复gb28181插件对时间戳的处理bug(引起跳秒等问题)
7.hdl插件对重连机制进行优化,对于服务器返回非200的连接不再重连
2022-12-31 22:22:10 +08:00
dexter
34394b99cf 1.增加http配置中的timeout参数,用于设置http响应的超时时间
2.http配置增加自定义中间件功能
3.hdl插件和jessica插件增加对http超时的处理
2022-12-19 13:53:32 +08:00
dexter
f56a6c972c 1.优化远程Pull和Push逻辑,增加日志输出
2.解决Stream中Json序列化循环引用问题
3.引擎增加对订阅者下线的及时响应
4.GB插件增加对AAC格式的推测
2022-12-19 09:47:24 +08:00
dexter
7138565443 engine升级到4.9.0,升级hls、hook、record、room
1.增加鉴权机制(钩子函数调用)
2.去除IO类型的泛型,增加代码可读性
3.引擎增加订阅者下线事件派发
4.增加配置项:鉴权开关、订阅者上下线事件开关、事件总线缓冲大小
5.修复网页无法播放HLS问题(修复TS格式写入PMT的PID值)
6.hook插件增加对订阅者下线的通知
7.record插件修复flv分片录制时的时间戳错误(现从0开始)
8.引擎现默认打开rtp乱序重排
2022-12-15 09:45:13 +08:00
langhuihui
a521c63036 fix: 最后订阅者离开条件判断,edge插件只能拉一路的bug 2022-12-11 15:39:21 +08:00
langhuihui
ed5e7196f7 fix: http-flv远端拉流h265编码格式解析 2022-12-10 22:05:27 +08:00
dexter
664a64a025 🐛 FIX: rtp缓存适配webrtc,修复录制插件重复录制问题以及崩溃报错 2022-12-06 09:33:32 +08:00
dexter
f097e66069 🐛 FIX: GB解决非同一域的目标地址问题,引擎增加SubscribeExist功能 2022-11-30 13:25:28 +08:00
dexter
2e8d4d2f3a 📦 NEW: 加入fMP4格式直播流播放功能 2022-11-27 22:25:58 +08:00
dexter
8bb63c9298 1.发布流首次超时时间随PublishTimeout配置
2.RTP打包较大AAC音频进行分包处理
3.截图插件默认不订阅音频提高速度
4.GB插件优化PS包解析兼容性
5.RTSP插件升级gortsplib版本
6.RTMP插件修复对外推流时流路径的设置
2022-11-17 23:52:14 +08:00
dexter
c0b2a10601 👌 IMPROVE: hls支持h265以及G711,Gb28181支持AAC 2022-11-13 23:55:11 +08:00
dexter
3179b892d6 🐛 FIX: hls的h265格式以及G711支持 2022-11-13 23:36:00 +08:00
dexter
ef3a85ec21 🐛 FIX: 修复h265的TS封装格式,修复RTP打包内存泄露 2022-11-10 20:08:05 +08:00
dexter
1a57aeb2a2 🐛 FIX: rtmp握手协议,SEI帧处理 2022-10-30 11:20:44 +08:00
dexter
85b134d333 👌 IMPROVE: 增加限速设置,修复gb单端口时音频时间戳单位 2022-10-26 11:08:05 +08:00
dexter
9969966b52 🐛 FIX: gb推测视频编码格式时判断错误 2022-10-24 14:21:25 +08:00
dexter
5482ab4157 👌 IMPROVE: 更先进的rtp包乱序重排算法 2022-10-23 19:29:34 +08:00
dexter
6a78440a61 👌 IMPROVE: 优化room插件 2022-10-19 23:37:35 +08:00
dexter
61eaaab78f 🐛 FIX: gb插件dts防止自动生成 2022-10-17 11:40:03 +08:00
dexter
54f01a064c 🐛 FIX: 控制台修改配置文件的功能 2022-10-14 16:54:40 +08:00
dexter
09570a6515 🐛 FIX: 录制功能 2022-10-13 20:08:35 +08:00
dexter
00ff25e173 🐛 FIX: 兼容go1.19 和m1 2022-10-13 18:49:12 +08:00
dexter
f0735d3883 Create FUNDING.yml 2022-10-11 20:03:48 +08:00
dexter
314191abaf update record plugin 2022-10-11 19:32:57 +08:00
dexter
33b5c465b6 🐛 FIX: 修复aac包比较小的情况下导致访问越界问题 2022-10-10 22:10:37 +08:00
langhuihui
2b1bf24fb9 🐛 修复logrotate插件windows下编译失败的bug 2022-10-08 21:46:17 +08:00
dexter
4653f7b511 👌 IMPROVE: 优化对外推流数据结构,优化engine对SSE的支持 2022-10-07 22:44:55 +08:00
dexter
86b16fe3fb 👌 IMPROVE: 远程控制台连接升级为quic协议 2022-10-06 00:07:57 +08:00
dexter
d05bd7bc55 📦 NEW: 新增edge插件,提供简单级联功能 2022-10-03 18:52:47 +08:00
dexter
6f9fe7c646 🐛 FIX: rtmp插件与srs服务器互通的问题 2022-09-24 21:08:06 +08:00
dexter
96d886c7a5 🐛 FIX: hook插件读取配置文件错误 2022-09-19 00:23:16 +08:00
dexter
ca2f69539d 🐛 FIX: gb拉流信令拼接错误 2022-09-16 19:19:00 +08:00
dexter
0fd608aefc 🐛 FIX: engine中订阅关键帧被覆盖问题,gb的ptzcmd中发送的deviceID值问题,acc的rtp包超过一个mtu时的解析问题 2022-09-16 13:40:29 +08:00
dexter
d5e6a517fb 🐛 FIX: dts在无B帧情况下的生成机制修正,gb插件增加设备信息持久化重启实例后可快速恢复 2022-09-12 23:02:01 +08:00
dexter
e035cf0846 👌 IMPROVE: 增加rtsp拉流报错信息,gb插件增加ps流dump功能方便排查错误 2022-09-11 21:15:47 +08:00
dexter
e7ff340c7a 🚀 RELEASE: 修复远端拉流保存并发问题,修复gb拉流PS包解析bug,导出部分插件 2022-09-04 22:23:40 +08:00
dexter
91d1d5502b Merge pull request #66 from dwdcth/patch-1
删除"## Changelog"
2022-09-02 16:00:01 +08:00
banshan
b1ef460af1 删除"## Changelog" 2022-09-02 15:53:08 +08:00
dexter
87f08066e5 API增加获取远端导入的流以及向远端推流的信息 2022-08-28 22:36:44 +08:00
dexter
da69aae5fa 修复gb插件中的pts和dts覆盖bug,兼容rtsp协议sdp中无sps和pps问题 2022-08-21 19:35:56 +08:00
dexter
6e8081a411 1、增加配置日志级别;2、发布者增加可以配置断线延迟退出时间配置 2022-08-13 18:33:02 +08:00
dexter
2f7bc14986 Merge pull request #63 from dwdcth/v4
添加版本号
2022-08-09 16:56:47 +08:00
hetangj
504e22b558 添加版本号 2022-08-09 16:43:01 +08:00
hetangj
c7b2cbfee3 '添加版本号' 2022-08-09 16:20:01 +08:00
dexter
70b5c553b9 修复数据轨道释放导致读取错误,以及写入annexb格式时的dts生成问题 2022-08-07 19:12:38 +08:00
dexter
4486c612e4 对rtmp协议写入数据增加判空逻辑 2022-07-30 16:22:44 +08:00
dexter
29f574ae73 增加10s的fps和bps历史统计,rtsp插件增加拉流时强制指定协议的配置,修复若干bug 2022-07-24 00:15:43 +08:00
dexter
d12052eb4a 修复gb28181插件ssrc取值以及ip获取的兼容性 2022-07-17 14:04:01 +08:00
dexter
7a0964b501 Merge branch 'v4' of github.com:langhuihui/monibuca into v4 2022-07-10 20:47:40 +08:00
dexter
67ad3e669d 更新gb插件以及远程控制台显示数据部分 2022-07-10 20:47:11 +08:00
dexter
6c78c2f456 Merge pull request #60 from dwdcth/v4
保存发布信息
2022-07-06 15:39:59 +08:00
banshan
16cf7725f3 保存发布信息 2022-07-06 15:35:26 +08:00
dexter
9d08fa47b3 修复startTime经过json序列化后变成负数的问题 2022-07-06 10:13:02 +08:00
dexter
9b6e9bfbec Merge pull request #59 from dwdcth/v4
修复goreleaser 发布时编译错误
2022-07-06 10:11:32 +08:00
banshan
a18fc77b8b Update go.yml 2022-07-06 09:39:46 +08:00
dexter
edd7a6f047 消除一个hls的内存泄露,修复gb插件的一个bug 2022-07-05 10:24:45 +08:00
dexter
9ec70f0454 升级engine 2022-06-29 14:23:04 +08:00
dexter
6ca479371e 修复若干bug 2022-06-27 00:41:32 +08:00
dexter
f65bd3a760 rtp相关追帧完成,修复音频数据写入bug 2022-06-25 20:53:25 +08:00
dexter
91a9171c5c preview插件加入索引页面,engine的api/plugins输出完整配置信息 2022-06-23 00:47:17 +08:00
dexter
0b51fecf48 snap插件更新 2022-06-20 13:25:50 +08:00
dexter
493bce72cb 取消flv补完,跳帧后的时间戳修复 2022-06-20 13:11:31 +08:00
dexter
a48057e713 更新engine依赖 2022-06-17 10:39:31 +08:00
dexter
2dd008d960 新增预览插件和截图插件,录像插件增加裸流保存功能 2022-06-12 15:51:04 +08:00
wancheng1990
3f7e7b3666 Update config.yaml 2022-06-08 16:29:55 +08:00
dexter
0791151557 quic-go版本不兼容 2022-06-08 13:26:08 +08:00
dexter
69f5d9d976 修复webrtc交互错误 2022-06-08 10:25:10 +08:00
dexter
dec17c1f26 gb28181默认mediaip取sipip配置,修复webrtc插件以及record插件报错 2022-06-05 21:56:59 +08:00
dexter
e6069322f8 修复大量bug,record增加分片录制能力,hls写盘功能移入record插件中 2022-05-31 11:16:14 +08:00
dexter
36ede9b9a2 fix: hls插件拉流panic错误 2022-05-16 19:02:41 +08:00
dexter
e4bc0ad5ea Merge pull request #51 from dwdcth/patch-3
Update go.yml
2022-05-16 10:14:38 +08:00
banshan
33a58b0641 Update go.yml 2022-05-16 10:12:50 +08:00
dexter
cb71b2c7e2 Merge pull request #50 from dwdcth/patch-1
Update go.yml
2022-05-15 21:24:11 +08:00
banshan
38b8923e6f Update go.yml 2022-05-15 21:20:41 +08:00
dexter
8b483e5139 升级插件版本 2022-05-15 19:12:30 +08:00
dexter
34c21e5e8f 尝试发布beta 2022-05-15 16:30:44 +08:00
dexter
b8a23517ba Merge pull request #48 from dwdcth/v4
publish langhuihui/m7s-import
2022-05-15 11:22:58 +08:00
banshan
d491b6206a publish langhuihui/m7s-import 2022-05-04 21:34:07 +08:00
dexter
93ce7391a5 Merge pull request #46 from dwdcth/v4
V4 自动发布和readme
2022-04-25 09:23:39 +08:00
dwdcth
611bd78b5c readme 2022-04-24 11:38:12 +08:00
dwdcth
b112187125 go.yml 2022-04-24 11:16:45 +08:00
dwdcth
e0c35fce2b v4 自动发布 2022-04-24 11:04:31 +08:00
banshan
7bcf2cba27 Update go.yml 2022-04-24 10:31:43 +08:00
dexter
da5f970067 Merge pull request #44 from dwdcth/v4
darwin and windows arm64
2022-04-24 09:41:51 +08:00
dwdcth
711b4cc82c darwin and windows arm64 2022-04-23 21:39:01 +08:00
dexter
f1bcb6f2c9 删除二进制文件 2022-04-08 16:18:39 +08:00
dexter
306a7ffeff 加入WebTransport插件 2022-04-08 16:07:00 +08:00
dexter
9c8a2c5d83 加入log和rtsp两个插件引入 2022-03-22 09:26:07 +08:00
dexter
1a67f63932 更新包路径 2022-02-27 21:47:51 +08:00
dexter
c761c6ce9b 简化代码 2022-02-23 17:34:26 +08:00
dexter
cc9c0d67e2 调试rtmp插件 2022-02-19 10:39:38 +08:00
dexter
6df2a3b842 修复window编译错误 2022-01-16 23:57:45 +08:00
dexter
c38bd36ecc 修复rtsp插件和webrtc插件的bug,windows环境下实现错误写入fatal.log的功能 2022-01-16 13:17:52 +08:00
dexter
a769f1cbf4 修复启动时版本显示不对的问题 2022-01-10 09:11:33 +08:00
dexter
8ec0515f60 hls增加过滤功能,以及永久m3u8文件生成 2022-01-06 14:45:47 +08:00
dexter
e2113e03df 解决rtp包含多slice的情况 2022-01-03 22:52:46 +08:00
dexter
56bbc0ddf8 支持配置文件保存功能,增加HDL拉取远端FLV流的功能 2021-12-27 21:12:05 +08:00
dexter
563f966fe4 修复engine中stream在publish的时候有空指针错误 2021-12-20 13:45:41 +08:00
dexter
0e3befd285 rtsp列表API修复 2021-12-13 10:21:54 +08:00
dexter
e60871b55b 增加查看流数据的功能 2021-12-11 20:07:05 +08:00
dexter
785be6a939 修复gb28181中音频时间戳换算 2021-12-02 11:31:16 +08:00
dexter
7684856ccc 修复gb插件乱序重排功能,对sps加强判断 2021-11-30 16:15:56 +08:00
dexter
2e76b74e74 增加RTP乱序重排,修复接收rtsp h265的推流、rtsp时间戳修复 2021-11-28 23:33:37 +08:00
dexter
e67b3f3bc8 修复timebase为0的情况 2021-11-24 13:51:22 +08:00
dexter
c17302a8fc 针对rtp包的B帧解析优化 2021-11-23 12:54:32 +08:00
dexter
02738a1482 rtsp库更换为gortsplib 2021-11-19 22:06:36 +08:00
dexter
0e1fe9a713 增加延时自动取消发布,gb28181增加tcp多端口监听功能 2021-11-13 15:26:26 +08:00
dexter
8a059c51a9 修复HLS时长浮点计算问题,以及webrtc插件依赖问题 2021-11-04 10:32:28 +08:00
dexter
07a34d2642 修复pushbytestream的bug以及gb28181插件对sip消息的from字段判空 2021-11-01 20:55:15 +08:00
dexter
7dca6c9411 修复引擎bug 2021-10-27 21:52:49 +08:00
dexter
eddbb35136 版本升级 2021-10-13 23:12:11 +08:00
dexter
65c3d90275 更新版本 2021-08-30 15:32:25 +08:00
dexter
531bcd57f1 更新版本 2021-08-22 23:32:36 +08:00
dexter
18bbedcff5 Merge branch '3.0' of github.com:langhuihui/monibuca into 3.0 2021-08-20 23:01:55 +08:00
dexter
6a2818916a 迁移UI到gateway插件中 2021-08-20 22:59:40 +08:00
wancheng1990
612501a403 更新ui 2021-08-19 13:54:39 +08:00
dexter
ca3397e544 Update go.yml 2021-08-18 16:47:58 +08:00
dexter
d01d254caa Update go.yml 2021-08-18 16:44:32 +08:00
dexter
7574a6ba6f 升级webrtc和hls版本 2021-08-16 21:20:19 +08:00
dexter
4c918df6e6 Merge branch '3.0' of github.com:langhuihui/monibuca into 3.0 2021-08-16 14:01:31 +08:00
dexter
f677beb2b7 升级到3.3.1 2021-08-16 14:01:22 +08:00
bosscheng
3adaedf6fc update ui 2021-08-07 00:08:25 +08:00
dexter
5a9cf53485 更新rtsp版本 2021-08-04 20:12:49 +08:00
dexter
bff77f1365 Merge branch '3.0' of github.com:langhuihui/monibuca into 3.0 2021-08-03 15:38:51 +08:00
dexter
b608c38aec 更新rtsp说明以及减少summary消息包大小 2021-08-03 15:38:46 +08:00
bosscheng
d8537ec3a3 update ui 2021-08-02 21:13:14 +08:00
dexter
c055f7fceb 更新版本优化gb28181 2021-08-02 09:03:21 +08:00
dexter
afb0efb2ee 版本升级 2021-07-31 16:25:22 +08:00
dexter
51832cd9a7 对两种ringbuffer采取不同策略 2021-07-29 14:46:59 +08:00
langhuihui
454e19f8c0 更新引擎版本 2021-07-23 20:56:16 +08:00
wancheng1990
62111b41d1 update ui 2021-07-22 16:00:48 +08:00
wancheng1990
0eca8fd657 update ui 2021-07-22 13:54:05 +08:00
langhuihui
ca16d405d0 修改默认配置 2021-07-20 16:59:49 +08:00
wancheng1990
e1d500447a update ui 2021-07-20 16:08:45 +08:00
wancheng1990
e7da10649c update ui 2021-07-20 15:36:19 +08:00
李宇翔
ccd0320090 更新版本 2021-07-13 09:24:01 +08:00
bosscheng
ee9d4d06b4 update ui 2021-07-12 23:11:53 +08:00
langhuihui
089cc593be 更新版本 2021-07-12 09:43:02 +08:00
bosscheng
9abd76b1e5 更新最新ui 2021-07-11 16:46:23 +08:00
langhuihui
6e65cc20a2 3.1.1 2021-07-11 10:49:38 +08:00
wancheng1990
ff71c7bd5a update ui audo play audio 2021-07-08 11:56:36 +08:00
wancheng1990
8648f2ae20 update ui 2021-06-30 18:27:57 +08:00
langhuihui
70e7fae954 更新GB28281版本 2021-06-29 14:09:17 +08:00
langhuihui
48b7ff1dee 更新版本 2021-06-28 23:48:30 +08:00
langhuihui
d46e0d45b2 更新版本 2021-06-27 08:18:29 +08:00
wancheng1990
5b41f67b80 update ui 2021-06-26 23:27:08 +08:00
wancheng1990
3a8b371560 update ui 2021-06-23 11:35:50 +08:00
langhuihui
8abae09574 更新版本 2021-06-18 22:00:58 +08:00
wancheng1990
476bb4a656 update ui 2021-06-15 17:18:56 +08:00
李宇翔
bf5b4010d7 更新UI和GB28181 2021-06-11 10:57:18 +08:00
李宇翔
8cee5c2c18 版本更新 2021-06-04 11:33:40 +08:00
李宇翔
43632cf852 更新插件版本及UI功能 2021-06-02 08:56:41 +08:00
李宇翔
e4e96f8fc1 更新gb28181插件版本 2021-05-31 08:38:10 +08:00
langhuihui
57acc5f357 更新gb28181插件 2021-05-30 16:40:16 +08:00
langhuihui
23faad4405 更新UI和GB28181 2021-05-26 22:05:06 +08:00
langhuihui
ec86b455a9 更新gb28181插件 2021-05-09 23:48:41 +08:00
langhuihui
f848e90a50 调整gb28181录像播放功能 2021-05-09 23:10:50 +08:00
langhuihui
7d05e4cc6c 更新版本 2021-05-09 21:35:39 +08:00
李宇翔
b66e52a90f 更新UI 2021-05-07 09:12:30 +08:00
langhuihui
da6554f536 修改readMe 2021-05-05 14:11:42 +08:00
langhuihui
2b8e8bca98 打包后传输 2021-05-05 11:38:12 +08:00
langhuihui
9009431a1f 更新git action 2021-05-04 17:19:35 +08:00
dexter
99b9cc674f Update go.yml 2021-05-04 16:35:13 +08:00
dexter
67b338007a Update go.yml 2021-05-04 10:50:43 +08:00
dexter
111ba8aa5a Update go.yml 2021-05-04 10:37:30 +08:00
dexter
d5ab178841 Update go.yml 2021-05-04 03:00:48 +08:00
dexter
3a3dd3c73c Update go.yml 2021-05-04 02:54:59 +08:00
dexter
75865960c6 Update go.yml 2021-05-04 02:44:34 +08:00
dexter
bb4c39886d Update go.yml 2021-05-04 02:29:47 +08:00
dexter
4e92f395bf Update go.yml 2021-05-04 01:55:37 +08:00
dexter
64cf6e609d Create go.yml 2021-05-04 01:35:18 +08:00
langhuihui
65bbb5c04f first commit 2021-05-04 01:08:09 +08:00
李宇翔
b0c6bff8ae 更新到1.16 2021-03-24 20:07:51 +08:00
dexter
439eb0afc0 Update README.md 2021-01-20 20:07:33 +08:00
langhuihui
f7e6c5084c 更新版本增加gb28181 2021-01-11 08:24:24 +08:00
李宇翔
e0f424fd2c 更新一下版本 2020-09-04 21:13:34 +08:00
dexter
98995de88a Merge pull request #6 from mask-pp/bug_fix
hold程序方式
2020-08-13 07:50:15 +08:00
workbase
b5c5976312 Merge branch '2.0' into bug_fix 2020-08-12 11:20:51 +08:00
mask-pp
c6dbda22f9 handle住程序方式 2020-08-12 11:17:48 +08:00
langhuihui
1e78f687d7 更新版本 2020-08-09 22:08:48 +08:00
langhuihui
701b9b63f3 更新二维码 2020-08-02 21:42:46 +08:00
langhuihui
58f134cda1 更新二维码 2020-07-26 21:50:09 +08:00
langhuihui
d772ed3336 更新二维码 2020-07-19 22:02:29 +08:00
langhuihui
fc4f13fa00 修改二维码版本 2020-07-12 22:40:16 +08:00
langhuihui
2ee71755b7 更新二维码 2020-07-05 21:56:19 +08:00
李宇翔
7e165fe9d4 更新版本以及二维码 2020-06-28 15:07:19 +08:00
langhuihui
dd7a2e8dc7 更新二维码 2020-06-21 21:56:41 +08:00
langhuihui
bd7f5249aa 更新二维码和引擎版本 2020-06-14 21:24:07 +08:00
langhuihui
bb9a154a00 修改ReadMe 更新插件版本 2020-06-07 19:57:13 +08:00
李宇翔
e3c7c9514e 更新go module和增加webrtc 2020-06-01 09:23:40 +08:00
李宇翔
b3a485ddfb 更新ReadMe 2020-06-01 09:14:38 +08:00
李宇翔
01a74b245e 修改版本号和二维码 2020-05-25 09:30:13 +08:00
dexter
4482f0232f update qr code 2020-05-18 09:07:37 +08:00
langhuihui
919f7ad78b 修改开源协议为MIT 2020-05-17 14:01:04 +08:00
langhuihui
0c1f1c9770 补充一个插件 2020-05-17 13:58:47 +08:00
langhuihui
18a9398f66 使用go module防止加载engine包失败 2020-05-16 23:47:23 +08:00
李宇翔
5828b8d803 增加协议实现的功能 2020-05-15 17:30:59 +08:00
李宇翔
c328db87a8 更新二维码 2020-05-11 09:12:05 +08:00
langhuihui
cc89bbb61b 删除所有子模块 2020-05-05 14:33:44 +08:00
李宇翔
cd34607c86 更新二维码 2020-04-30 14:09:44 +08:00
李宇翔
cfb6dacf73 更新二维码 2020-04-22 15:03:53 +08:00
dexter
eba5b6b8f1 Update README_zh.md 2020-04-15 15:24:37 +08:00
dexter
597407a4e5 Update README.md 2020-04-15 15:23:50 +08:00
dexter
3fcd22d1e7 Update README_zh.md 2020-04-08 15:14:36 +08:00
dexter
65de4806c4 Update README.md 2020-04-08 15:13:31 +08:00
dexter
55eb2218be 更新二维码 2020-04-01 17:09:25 +08:00
李宇翔
5c0b22b1df 增加一键安装说明 2020-03-26 15:49:01 +08:00
李宇翔
474581d9b1 更新群二维码 2020-03-25 19:27:07 +08:00
李宇翔
3907422b3e 加个图片 2020-03-19 11:53:57 +08:00
李宇翔
d3b7fa04ef 修改文档 2020-03-19 11:47:36 +08:00
李宇翔
d16aa756f8 Merge branch 'master' of https://github.com/langhuihui/monibuca 2020-03-18 18:33:31 +08:00
李宇翔
8304314e49 英文readme开发 2020-03-18 18:32:25 +08:00
langhuihui
c2d0cbb1d0 修改获取配置文件的方法 2020-03-13 15:30:12 +08:00
李宇翔
67ab443f1a 更新readme 2020-03-11 16:18:50 +08:00
langhuihui
c80beec8f3 main.go修改为demo实例 2020-03-07 16:21:23 +08:00
langhuihui
1ce4fd2db0 转换为子模块方式 2020-03-07 14:16:38 +08:00
dexter
a20f30d76d 刷新二维码 2020-03-04 09:52:48 +08:00
langhuihui
afa44eb173 迁移说明 2020-03-01 20:02:34 +08:00
李宇翔
592034adce 加入Q&A 2020-02-27 14:02:28 +08:00
langhuihui
6e8ac88aaf 修复bug 2020-02-26 20:19:33 +08:00
李宇翔
da66df9302 添加微信交流群二维码 2020-02-26 18:20:34 +08:00
李宇翔
360f7faf35 编译静态资源 2020-02-26 16:43:20 +08:00
李宇翔
bee477e685 加入插件市场功能 2020-02-26 15:54:30 +08:00
李宇翔
4a0c4e36e7 正在加入插件市场查询 2020-02-26 15:54:30 +08:00
langhuihui
1ed3e6249f 插件市场开发 2020-02-26 15:54:29 +08:00
langhuihui
1ac06ef252 完成H265支持以及mp3格式支持 2020-02-24 23:21:00 +08:00
李宇翔
620d96b39c 预览加入h265和mp3的解码支持 2020-02-24 11:56:25 +08:00
langhuihui
66a396df0b 重启脚本修改 2020-02-21 16:01:46 +08:00
langhuihui
029abe3187 加入设计原理文档 2020-02-21 10:09:37 +08:00
langhuihui
40fba0d348 支持PES中包含多个AAC帧 2020-02-19 14:58:21 +08:00
langhuihui
73941d1e0b ADTS转RTMP协议方式传输音频包 2020-02-18 15:19:21 +08:00
langhuihui
709c2c6ac7 增加目录候选功能 2020-02-17 11:48:58 +08:00
langhuihui
f96bc11ddb 增强对实例的控制 2020-02-15 21:07:47 +08:00
langhuihui
5563ddc0d2 增强对实例的控制 2020-02-14 09:54:53 +08:00
langhuihui
95657bd6df 增强对实例的控制 2020-02-13 17:41:39 +08:00
langhuihui
b9e19e75c8 增强对实例的控制 2020-02-13 10:43:32 +08:00
langhuihui
eac623639d 界面增加重启和升级 2020-02-11 21:59:31 +08:00
langhuihui
fea6e98ca7 小功能增加 2020-02-11 17:27:05 +08:00
langhuihui
649a5b558a 增加实例管理器 2020-02-11 14:53:29 +08:00
langhuihui
b3c8d35fad 修复集群传输bug 2020-02-05 17:29:55 +08:00
langhuihui
ab745145d9 集群采集信息功能完善 2020-02-04 16:09:55 +08:00
144 changed files with 1056 additions and 37958 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: monibuca
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

76
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: Go
on:
create:
tags:
- v4*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Set up Env
run: echo "version=${GITHUB_REF:11}" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Cache Go modules
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: runner.osgo{ { hashFiles('**/go.sum') } }
restore-keys: ${{ runner.os }}-go-
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
if: success() && startsWith(github.ref, 'refs/tags/')
with:
version: v1.8.3
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout m7s-import
uses: actions/checkout@v3
with:
repository: langhuihui/m7s-import
path: m7s-import
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0
- name: Add bin to m7s-import
if: success() && startsWith(github.ref, 'refs/tags/')
run: |
cd m7s-import
mkdir -p apps/m7s-website/src/public/bin
cp ../dist/m7s_${{ env.version }}_windows_amd64.tar.gz apps/m7s-website/src/public/bin/m7s_windows_amd64.tar.gz
cp ../dist/m7s_${{ env.version }}_darwin_amd64.tar.gz apps/m7s-website/src/public/bin/m7s_darwin_amd64.tar.gz
cp ../dist/m7s_${{ env.version }}_darwin_arm64.tar.gz apps/m7s-website/src/public/bin/m7s_darwin_arm64.tar.gz
cp ../dist/m7s_${{ env.version }}_linux_amd64.tar.gz apps/m7s-website/src/public/bin/m7s_linux_amd64.tar.gz
cp ../dist/m7s_${{ env.version }}_linux_arm64.tar.gz apps/m7s-website/src/public/bin/m7s_linux_arm64.tar.gz
releaseinfo=`curl -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest`
body=`echo $releaseinfo | jq ".body" | sed -e 's/^"//' -e 's/"$//'`
name=`echo $releaseinfo | jq ".name" | sed -e 's/^"//' -e 's/"$//'`
touch apps/m7s-website/src/release.txt
echo -e "# $name\n$body" | cat - apps/m7s-website/src/release.txt > temp && mv temp apps/m7s-website/src/release.txt
sed "/## Changelog/d" -i apps/m7s-website/src/release.txt
git config --global user.name 'langhuihui'
git config --global user.email '178529795@qq.com'
git add -A
git commit -m '[automated commit] add bin & GitHub workflow'
- name: push to m7s-import
uses: ad-m/github-push-action@master
if: success() && startsWith(github.ref, 'refs/tags/')
with:
github_token: ${{ secrets.TOKEN }}
directory: m7s-import
repository: langhuihui/m7s-import

23
.gitignore vendored
View File

@@ -1,4 +1,25 @@
*.exe
.vscode
.idea
resource
resource
*.log
/monibuca
node_modules
shutdown.bat
*.sh
.m7s
debug.go
dist/
record/
config2.yaml
config1.yaml
monibuca_*
ps
ps2
ps3
devices.json
dump
.DS_Store
main
monitor
.history

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
#源镜像
FROM alpine:latest
WORKDIR /opt
ADD monibuca /opt
ADD local.monibuca.com.key /opt
ADD local.monibuca.com_bundle.pem /opt
#暴露端口
EXPOSE 8080 8081 1935 554 58200 5060 8000-9000
EXPOSE 5060/udp 58200/udp 8000-9000/udp
#最终运行docker的命令
ENTRYPOINT ["./m7s"]

695
LICENSE
View File

@@ -1,674 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
The MIT License (MIT)
Copyright (c) 2019-present, dexter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

138
README.md
View File

@@ -1 +1,137 @@
[中文文档](http://monibuca.com/docs)
# Official website
[https://m7s.live](https://m7s.live)
# 中文文档
[https://m7s.live/guide/introduction.html](https://m7s.live/guide/introduction.html)
# Core code base and plug-in code base
[https://github.com/Monibuca](https://github.com/Monibuca)
# Introduction
## What is Monibuca (m7s)?
Monibuca (pronounced: analog not card, m7s is its abbreviation, similar to k8s) is an open source streaming server development framework developed in Go. It is based on go1.19+, in addition to no other dependencies built, and provides a set of plug-in secondary development model to help you efficiently develop streaming media servers, you can directly use the official plug-in, or develop your own plug-in to extend any function, so Monibuca is a framework that can support any streaming protocol!
Monibuca consists of three parts: engine, plugins, and instance project.
The engine provides a common streaming data cache and forwarding mechanism, and does not care how the protocol is implemented
The plugins offer all the other features and can be extended indefinitely
An instance project is a project project that introduces the engine and plugins and starts the engine, and can be written entirely by yourself
## Plug-in framework
Monibuca aims to build a general streaming media development ecosystem, so since the v1 version, it has considered the decoupling of services and stream forwarding, so as to design a set of plug-in mechanisms that can be arbitrarily extended. Depending on your needs, you can flexibly introduce different types of plugins:
- Provide streaming media protocol packaging/unpacking, such as RTMP plug-ins, RTSP plug-ins, etc
- Provides log persistence processing - logrotate plugin
- Provide recording function - record plugin
- Provide rich debugging functions - debug plugin
- Provide HTTP callback capability - hook plugin
If you are an experienced developer, then the best way is to carry out secondary development on the basis of existing plugins, and provide reusable plugins to more people to enrich the ecosystem. If you're a beginner in streaming, the best way to do this is to use existing plugins to cobble together the features you need and ask experienced developers for help.
# Key features
## Engine aspect
- Provides a plug-in mechanism to manage plug-in startup, configuration resolution, event distribution, etc. in a unified manner
- Provide forwarding in H264, H265, AAC, G711 format
- Provide reusable AVCC format, RTP format, AnnexB format, ADTS format and other pre-encapsulation mechanisms
- Provides a multi-track mechanism, supports large and small streams, and encrypts stream expansion
- Provide DataTrack mechanism, which can be used to implement functions such as room text chat
- Provide timestamp synchronization mechanism and speed limit mechanism
- Provides an RTP packet reorder mechanism
- Provide subscriber frame chasing and skipping mechanism (first screen second on)
- Provides the infrastructure for publish-subscribe push and pull out
- Provides underlying architecture support for authentication mechanisms
- Provides a memory reuse mechanism
- Provides a mechanism for publishers to disconnect and reconnect
- Provides an on-demand flow pulling mechanism
- Provides a common mechanism for HTTP service ports
- Provides an automatic registration mechanism for HTTP API interfaces
- Provides HTTP interface middleware mechanism
- Provides structured logs
- Provides flow information statistics and output
- Provides an event bus mechanism that broadcasts events to all plug-ins
- Provides a configuration hot update mechanism
## Plug-in aspect
- Provide RTMP protocol push-pull stream, external push-pull stream (RTMPS supported)
- Provides RTSP push and pull streams and external push and pull streams
- Provides HTTP-FLV protocol to pull streams, pull external streams, and read local FLV files
- Provides streaming of the WebSocket protocol
- Provides HLS protocol to pull streams and pull outflows
- Provides push-pull streams for the WebRTC protocol
- Provides GB28181 protocol push and dump playback analysis capabilities
- Provide support for the Onif protocol
- Provides streaming of WebTransport protocol
- Provides FMP4 protocol for pulling streams
- Provides edge server functionality to implement cascading streaming
- Provide video recording function, support FLV, MP4, HLS, RAW formats
- Provides log persistence by day, hour, minute, second, size, and number of files
- Provide a screenshot function
- Provides HTTP callback function
- Preview features available (integrated with Jessibuca Pro)
- Room function available (video conferencing possible)
- Provide the function of docking with Prometheus
Third-party plugins and paid plugins provide additional functionality and are not listed here.
Inspired by:
- [mp4ff](https://github.com/edgeware/mp4ff) mp4 file format library [@edgeware](https://github.com/edgeware)
- [gosip](https://github.com/ghettovoice/gosip) go sip library [@ghettovoice](https://github.com/ghettovoice)
- [webrtc](https://github.com/pion/webrtc) go library and whole [@pion](https://github.com/pion) team
- [gortsplib](https://github.com/aler9/gortsplib) rtsp library [@aler9](https://github.com/aler9)
## Remote console
- Provides multi-instance management
- Provide flow details display
- Provides visual editing of configurations
- Provides visual display of logs
- Provide visual management of plugins
- Provides GB device management
- Provides an interface for dynamically adding remote push-pull flows
- Provide WebRTC background wall function
- Provide multiplayer video demonstrations
# Origin of the name
The word Monibuca is derived from (Monica), and in order to solve the naming problem, three names are used to represent server, player, and streamer. Since Monica, Jessica, and Rebecca all have `卡` words, which is not good for the live broadcast (ca - `卡` means block in Chinese), it was changed to Monibuca, Jessibuca(https://jessibuca.com), and Rebebuca(https://rebebuca.com). (bu-`不` means not)
# Install
- The compiled binary executable files (i.e. green software) of each platform are officially provided, so it can run without installing any other software.
- If you need to compile and start the project yourself, you need to install go1.19 or above.
The official download link of the latest version is provided:
- [Linux](https://download.m7s.live/bin/m7s_linux_arm64.tar.gz)
- [Linux-arm64](https://download.m7s.live/bin/m7s_linux_arm64.tar.gz)
- [Mac](https://download.m7s.live/bin/m7s_darwin_arm64.tar.gz)
- [Mac-arm64](https://download.m7s.live/bin/m7s_darwin_arm64.tar.gz)
- [Windows](https://download.m7s.live/bin/m7s_windows_amd64.tar.gz)
Don't forget to fix the rights chmod +x m7s_xxx_xxx on Linux and Mac.
# Run
## Executable files run directly
- Linux, for example, downloaded to `/opt/m7s_linux_x86`, then `$ cd /opt ' and then `$ ./m7s_linux_x86`
- Similar to Linux and Mac, you may need to modify the executable permissions of the file or double-click to run
- Windows, double-click m7s directly_windows_x86.exe can be started
:::tip 运行多实例
由于实例会监听http端口所以如果需要运行多实例就需要为每个实例指定不同的http端口因此需要启动时指定配置文件例如./m7s_linux_x86 -c config.yaml
:::
## Self-compiled startup project
1. `git clone https://github.com/langhuihui/monibuca`
2. `cd monibuca`
3. `go run .`
## Self-created startup project
You can watch the video tutorial:
- [从零启动 m7s V4](https://www.bilibili.com/video/BV1iq4y147N4/)
- [m7s v4 视频教程——插件引入](https://www.bilibili.com/video/BV1sP4y1g7BF/)

View File

@@ -1,18 +0,0 @@
[Plugins.HDL]
ListenAddr = ":2020"
[Plugins.Jessica]
ListenAddr = ":8080"
[Plugins.RTMP]
ListenAddr = ":1935"
[Plugins.GateWay]
ListenAddr = ":8081"
[Plugins.Cluster]
Master = "203.60.1.23:2019"
#ListenAddr = ":2019"
#
#[Plugins.Auth]
#Key="www.monibuca.com"
[Plugins.RecordFlv]
Path="./resource"
[Plugins.QoS]
Suffix = ["high","medium","low"]

35
config.yaml Normal file
View File

@@ -0,0 +1,35 @@
# 支持零配置启动,即无需填写配置默认启用所有的插件。
# 只需要填写需要修改的配置项即可。不需要将所有的配置都填写进来!!。
# 全局配置参考 https://m7s.live/guide/config.html
# 插件配置参考各个插件的文档
# 插件都有一个enable配置如果为false则不启用该插件默认为true即不需要配置。
# global:
# console:
# secret: "ab0f6913670062af4d2f15c621205178"
# http:
# listenaddrtls: :8081
# certfile: local.monibuca.com_bundle.pem
# keyfile: local.monibuca.com.key
# record:
# enable: false
# hls:
# autorecord: true
# fragment: 20s
# webtransport:
# enable: false
# certfile: local.monibuca.com_bundle.pem
# keyfile: local.monibuca.com.key
# gb28181:
# # enable: false
# rtsp:
# # enable: false
# pull:
# pullonstart:
# live/rtsp: rtsp://admin:MOMA2021@47.94.226.17/Streaming/Channels/201?transportmode=unicast
# hls:
# # enable: false
# pull:
# pullonstart: true
# pulllist:
# live/hls: http://111.40.196.27/PLTV/88888888/224/3221225548/index.m3u8

11
config2.yaml Normal file
View File

@@ -0,0 +1,11 @@
global:
http:
listenaddr: :8080
rtsp:
listenaddr: :8554
rtmp:
enable: false
webtransport:
enable: false
hls:
enable: false

21
dashboard/.gitignore vendored
View File

@@ -1,21 +0,0 @@
.DS_Store
node_modules
public/docs
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,24 +0,0 @@
# dashboard
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -1,3 +0,0 @@
module.exports = {
}

View File

@@ -1 +0,0 @@
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}.records[data-v-4eee1624]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 15px}.records>[data-v-4eee1624]{width:200px}.log-container{overflow-y:auto;max-height:500px}@-webkit-keyframes recording-data-v-f6113870{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-f6113870{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-f6113870]{-webkit-animation:recording-data-v-f6113870 1s infinite;animation:recording-data-v-f6113870 1s infinite}.layout[data-v-f6113870]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-f6113870]{width:250px;margin:10px;text-align:left}.empty[data-v-f6113870]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-f6113870],.status[data-v-f6113870]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-f6113870]{position:fixed;left:5px;bottom:10px}.status>div[data-v-f6113870]{margin:0 5px}

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><div class="content"><h1>404</h1><blockquote>Looks like we've got some broken links.</blockquote><a href="/docs/" class="router-link-active">Take me home.</a></div></div></div>
<script src="/docs/assets/js/app.d51c815b.js" defer></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg>

Before

Width:  |  Height:  |  Size: 216 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{165:function(t,e,s){"use strict";s.r(e);var n=s(0),i=Object(n.a)({},(function(){var t=this.$createElement;this._self._c;return this._m(0)}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"content"},[e("h1",{attrs:{id:"更新历史"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#更新历史","aria-hidden":"true"}},[this._v("#")]),this._v(" 更新历史")]),e("ul",[e("li",[this._v("2020/1/27\n初步完成")])])])}],!1,null,null,null);e.default=i.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,189 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>插件开发 | Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/2.190ec46a.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
Monibuca
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="active sidebar-link">插件开发</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的定义" class="sidebar-link">插件的定义</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的安装" class="sidebar-link">插件的安装</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发订阅者插件" class="sidebar-link">开发订阅者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发发布者插件" class="sidebar-link">开发发布者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发钩子插件" class="sidebar-link">开发钩子插件</a></li></ul></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="插件开发"><a href="#插件开发" aria-hidden="true" class="header-anchor">#</a> 插件开发</h1><h2 id="插件的定义"><a href="#插件的定义" aria-hidden="true" class="header-anchor">#</a> 插件的定义</h2><p>所谓的插件,没有什么固定的规则,只需要完成<code>安装</code>操作即可。插件可以实现任意的功能扩展,最常见的是实现某种传输协议用来推流或者拉流</p><h2 id="插件的安装"><a href="#插件的安装" aria-hidden="true" class="header-anchor">#</a> 插件的安装</h2><p>下面是内置插件jessica的源码代表了典型的插件安装</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> jessica
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token punctuation">.</span> <span class="token string">&quot;github.com/langhuihui/monibuca/monica&quot;</span>
<span class="token string">&quot;log&quot;</span>
<span class="token string">&quot;net/http&quot;</span>
<span class="token punctuation">)</span>
<span class="token keyword">var</span> config <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>ListenerConfig<span class="token punctuation">)</span>
<span class="token keyword">func</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">InstallPlugin</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>PluginConfig<span class="token punctuation">{</span>
Name<span class="token punctuation">:</span> <span class="token string">&quot;Jessica&quot;</span><span class="token punctuation">,</span>
Type<span class="token punctuation">:</span> PLUGIN_SUBSCRIBER<span class="token punctuation">,</span>
Config<span class="token punctuation">:</span> config<span class="token punctuation">,</span>
Run<span class="token punctuation">:</span> run<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;server Jessica start at %s&quot;</span><span class="token punctuation">,</span> config<span class="token punctuation">.</span>ListenAddr<span class="token punctuation">)</span>
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>ListenAddr<span class="token punctuation">,</span> http<span class="token punctuation">.</span><span class="token function">HandlerFunc</span><span class="token punctuation">(</span>WsHandler<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当主程序读取配置文件完成解析后会调用各个插件的Run函数上面代码中执行了一个http的端口监听</p><h2 id="开发订阅者插件"><a href="#开发订阅者插件" aria-hidden="true" class="header-anchor">#</a> 开发订阅者插件</h2><p>所谓订阅者就是用来从流媒体服务器接收音视频流的程序例如RTMP协议执行play命令后、http-flv请求响应程序、websocket响应程序。内置插件中录制flv程序也是一个特殊的订阅者。
下面是http-flv插件的源码供参考</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> HDL
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token punctuation">.</span> <span class="token string">&quot;github.com/langhuihui/monibuca/monica&quot;</span>
<span class="token string">&quot;github.com/langhuihui/monibuca/monica/avformat&quot;</span>
<span class="token string">&quot;github.com/langhuihui/monibuca/monica/pool&quot;</span>
<span class="token string">&quot;log&quot;</span>
<span class="token string">&quot;net/http&quot;</span>
<span class="token string">&quot;strings&quot;</span>
<span class="token punctuation">)</span>
<span class="token keyword">var</span> config <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>ListenerConfig<span class="token punctuation">)</span>
<span class="token keyword">func</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">InstallPlugin</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>PluginConfig<span class="token punctuation">{</span>
Name<span class="token punctuation">:</span> <span class="token string">&quot;HDL&quot;</span><span class="token punctuation">,</span>
Type<span class="token punctuation">:</span> PLUGIN_SUBSCRIBER<span class="token punctuation">,</span>
Config<span class="token punctuation">:</span> config<span class="token punctuation">,</span>
Run<span class="token punctuation">:</span> run<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;HDL start at %s&quot;</span><span class="token punctuation">,</span> config<span class="token punctuation">.</span>ListenAddr<span class="token punctuation">)</span>
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>ListenAddr<span class="token punctuation">,</span> http<span class="token punctuation">.</span><span class="token function">HandlerFunc</span><span class="token punctuation">(</span>HDLHandler<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">HDLHandler</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">{</span>
sign <span class="token operator">:=</span> r<span class="token punctuation">.</span>URL<span class="token punctuation">.</span><span class="token function">Query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">&quot;sign&quot;</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">:=</span> AuthHooks<span class="token punctuation">.</span><span class="token function">Trigger</span><span class="token punctuation">(</span>sign<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
w<span class="token punctuation">.</span><span class="token function">WriteHeader</span><span class="token punctuation">(</span><span class="token number">403</span><span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
stringPath <span class="token operator">:=</span> strings<span class="token punctuation">.</span><span class="token function">TrimLeft</span><span class="token punctuation">(</span>r<span class="token punctuation">.</span>RequestURI<span class="token punctuation">,</span> <span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> strings<span class="token punctuation">.</span><span class="token function">HasSuffix</span><span class="token punctuation">(</span>stringPath<span class="token punctuation">,</span> <span class="token string">&quot;.flv&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
stringPath <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">TrimRight</span><span class="token punctuation">(</span>stringPath<span class="token punctuation">,</span> <span class="token string">&quot;.flv&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token boolean">_</span><span class="token punctuation">,</span> ok <span class="token operator">:=</span> AllRoom<span class="token punctuation">.</span><span class="token function">Load</span><span class="token punctuation">(</span>stringPath<span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span>
<span class="token comment">//atomic.AddInt32(&amp;hdlId, 1)</span>
w<span class="token punctuation">.</span><span class="token function">Header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Set</span><span class="token punctuation">(</span><span class="token string">&quot;Transfer-Encoding&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;chunked&quot;</span><span class="token punctuation">)</span>
w<span class="token punctuation">.</span><span class="token function">Header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Set</span><span class="token punctuation">(</span><span class="token string">&quot;Content-Type&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;video/x-flv&quot;</span><span class="token punctuation">)</span>
w<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span>avformat<span class="token punctuation">.</span>FLVHeader<span class="token punctuation">)</span>
p <span class="token operator">:=</span> OutputStream<span class="token punctuation">{</span>
Sign<span class="token punctuation">:</span> sign<span class="token punctuation">,</span>
SendHandler<span class="token punctuation">:</span> <span class="token keyword">func</span><span class="token punctuation">(</span>packet <span class="token operator">*</span>pool<span class="token punctuation">.</span>SendPacket<span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> avformat<span class="token punctuation">.</span><span class="token function">WriteFLVTag</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> packet<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
SubscriberInfo<span class="token punctuation">:</span> SubscriberInfo<span class="token punctuation">{</span>
ID<span class="token punctuation">:</span> r<span class="token punctuation">.</span>RemoteAddr<span class="token punctuation">,</span> Type<span class="token punctuation">:</span> <span class="token string">&quot;FLV&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
p<span class="token punctuation">.</span><span class="token function">Play</span><span class="token punctuation">(</span>stringPath<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
w<span class="token punctuation">.</span><span class="token function">WriteHeader</span><span class="token punctuation">(</span><span class="token number">404</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>其中核心逻辑就是创建OutputStream对象每一个订阅者需要提供SendHandler函数用来接收来自发布者广播出来的音视频数据。
最后调用该对象的Play函数进行播放。请注意Play函数会阻塞当前goroutine。</p><h2 id="开发发布者插件"><a href="#开发发布者插件" aria-hidden="true" class="header-anchor">#</a> 开发发布者插件</h2><p>所谓发布者就是提供音视频数据的程序例如接收来自OBS、ffmpeg的推流的程序。内置插件中集群功能里面有一个特殊的发布者它接收来自源服务器的音视频数据然后在本服务器中广播音视频。
以此为例,我们需要提供一个结构体定义来表示特定的发布者:</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">type</span> Receiver <span class="token keyword">struct</span> <span class="token punctuation">{</span>
InputStream
io<span class="token punctuation">.</span>Reader
<span class="token operator">*</span>bufio<span class="token punctuation">.</span>Writer
<span class="token punctuation">}</span>
</code></pre></div><p>其中InputStream 是固定的,必须包含,且必须以组合继承的方式定义。其余的成员则是任意的。
发布者的发布动作需要特定条件的触发,例如在集群插件中,当本服务器有订阅者订阅了某个流,而该流并没有发布者的时候就会触发向源服务器拉流的函数:</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">PullUpStream</span><span class="token punctuation">(</span>streamPath <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
addr<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">ResolveTCPAddr</span><span class="token punctuation">(</span><span class="token string">&quot;tcp&quot;</span><span class="token punctuation">,</span> config<span class="token punctuation">.</span>Master<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token function">MayBeError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
conn<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">DialTCP</span><span class="token punctuation">(</span><span class="token string">&quot;tcp&quot;</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> addr<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token function">MayBeError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
brw <span class="token operator">:=</span> bufio<span class="token punctuation">.</span><span class="token function">NewReadWriter</span><span class="token punctuation">(</span>bufio<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>conn<span class="token punctuation">)</span><span class="token punctuation">,</span> bufio<span class="token punctuation">.</span><span class="token function">NewWriter</span><span class="token punctuation">(</span>conn<span class="token punctuation">)</span><span class="token punctuation">)</span>
p <span class="token operator">:=</span> <span class="token operator">&amp;</span>Receiver<span class="token punctuation">{</span>
Reader<span class="token punctuation">:</span> conn<span class="token punctuation">,</span>
Writer<span class="token punctuation">:</span> brw<span class="token punctuation">.</span>Writer<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> p<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>streamPath<span class="token punctuation">,</span> p<span class="token punctuation">)</span> <span class="token punctuation">{</span>
p<span class="token punctuation">.</span><span class="token function">WriteByte</span><span class="token punctuation">(</span>MSG_SUBSCRIBE<span class="token punctuation">)</span>
p<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>streamPath<span class="token punctuation">)</span>
p<span class="token punctuation">.</span><span class="token function">WriteByte</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
p<span class="token punctuation">.</span><span class="token function">Flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> v <span class="token operator">:=</span> <span class="token keyword">range</span> p<span class="token punctuation">.</span>Subscribers <span class="token punctuation">{</span>
p<span class="token punctuation">.</span><span class="token function">Auth</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">defer</span> p<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token punctuation">{</span>
cmd<span class="token punctuation">,</span> err <span class="token operator">:=</span> brw<span class="token punctuation">.</span><span class="token function">ReadByte</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token function">MayBeError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">switch</span> cmd <span class="token punctuation">{</span>
<span class="token keyword">case</span> MSG_AUDIO<span class="token punctuation">:</span>
<span class="token keyword">if</span> audio<span class="token punctuation">,</span> err <span class="token operator">:=</span> p<span class="token punctuation">.</span><span class="token function">readAVPacket</span><span class="token punctuation">(</span>avformat<span class="token punctuation">.</span>FLV_TAG_TYPE_AUDIO<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
p<span class="token punctuation">.</span><span class="token function">PushAudio</span><span class="token punctuation">(</span>audio<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">case</span> MSG_VIDEO<span class="token punctuation">:</span>
<span class="token keyword">if</span> video<span class="token punctuation">,</span> err <span class="token operator">:=</span> p<span class="token punctuation">.</span><span class="token function">readAVPacket</span><span class="token punctuation">(</span>avformat<span class="token punctuation">.</span>FLV_TAG_TYPE_VIDEO<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token operator">&amp;&amp;</span> <span class="token function">len</span><span class="token punctuation">(</span>video<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">2</span> <span class="token punctuation">{</span>
tmp <span class="token operator">:=</span> video<span class="token punctuation">.</span>Payload<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token comment">// 第一个字节保存着视频的相关信息.</span>
video<span class="token punctuation">.</span>VideoFrameType <span class="token operator">=</span> tmp <span class="token operator">&gt;&gt;</span> <span class="token number">4</span> <span class="token comment">// 帧类型 4Bit, H264一般为1或者2</span>
p<span class="token punctuation">.</span><span class="token function">PushVideo</span><span class="token punctuation">(</span>video<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">case</span> MSG_AUTH<span class="token punctuation">:</span>
cmd<span class="token punctuation">,</span> err <span class="token operator">=</span> brw<span class="token punctuation">.</span><span class="token function">ReadByte</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token function">MayBeError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
bytes<span class="token punctuation">,</span> err <span class="token operator">:=</span> brw<span class="token punctuation">.</span><span class="token function">ReadBytes</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token function">MayBeError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
subId <span class="token operator">:=</span> strings<span class="token punctuation">.</span><span class="token function">Split</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>bytes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token function">len</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">&quot;,&quot;</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>
<span class="token keyword">if</span> v<span class="token punctuation">,</span> ok <span class="token operator">:=</span> p<span class="token punctuation">.</span>Subscribers<span class="token punctuation">[</span>subId<span class="token punctuation">]</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span>
<span class="token keyword">if</span> cmd <span class="token operator">!=</span> <span class="token number">1</span> <span class="token punctuation">{</span>
v<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>正在该函数中会向源服务器建立tcp连接然后发送特定命令表示需要拉流当我们接收到源服务器的数据的时候就调用PushVideo和PushAudio函数来广播音视频。</p><p>核心逻辑是调用InputStream的Publish以及PushVideo、PushAudio函数</p><h2 id="开发钩子插件"><a href="#开发钩子插件" aria-hidden="true" class="header-anchor">#</a> 开发钩子插件</h2><p>钩子插件就是在服务器的关键逻辑处插入的函数调用,方便扩展服务器的功能,比如对连接进行验证,或者触发一些特殊的发布者。
目前提供的钩子包括</p><ul><li>当发布者开始发布时 <code>OnPublishHooks.AddHook(onPublish)</code>
例如:</li></ul><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">onPublish</span><span class="token punctuation">(</span>r <span class="token operator">*</span>Room<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> v <span class="token operator">:=</span> <span class="token keyword">range</span> r<span class="token punctuation">.</span>Subscribers <span class="token punctuation">{</span>
<span class="token keyword">if</span> err <span class="token operator">:=</span> <span class="token function">CheckSign</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span>Sign<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
v<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>此时可以访问房间里面的订阅者,对其进行验证。</p><ul><li>当有订阅者订阅了某个流时,<code>OnSubscribeHooks.AddHook(onSubscribe)</code>
例如:</li></ul><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">onSubscribe</span><span class="token punctuation">(</span>s <span class="token operator">*</span>OutputStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> s<span class="token punctuation">.</span>Publisher <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">go</span> <span class="token function">PullUpStream</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>StreamPath<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>拉取源服务器的流</p></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
<a href="/docs/" class="prev router-link-active">
起步
</a></span><span class="next"><a href="/docs/history.html">
更新日志
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/2.190ec46a.js" defer></script>
</body>
</html>

View File

@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>更新历史 | Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/3.7646e76c.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
Monibuca
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="active sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="更新历史"><a href="#更新历史" aria-hidden="true" class="header-anchor">#</a> 更新历史</h1><ul><li>2020/1/27
初步完成</li></ul></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
<a href="/docs/develop.html" class="prev">
插件开发
</a></span><span class="next"><a href="/docs/plugins.html">
内置插件
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/3.7646e76c.js" defer></script>
</body>
</html>

View File

@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Monibuca快速起步 | Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/1.6babbc1d.js" as="script"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-exact-active router-link-active"><!----><span class="site-name">
Monibuca
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="active sidebar-link">起步</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/#介绍" class="sidebar-link">介绍</a></li><li class="sidebar-sub-header"><a href="/docs/#启动" class="sidebar-link">启动</a></li><li class="sidebar-sub-header"><a href="/docs/#配置" class="sidebar-link">配置</a></li></ul></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="monibuca快速起步"><a href="#monibuca快速起步" aria-hidden="true" class="header-anchor">#</a> Monibuca快速起步</h1><h2 id="介绍"><a href="#介绍" aria-hidden="true" class="header-anchor">#</a> 介绍</h2><p>Monibuca 是一个开源的流媒体服务器开发框架适用于快速定制化开发流媒体服务器可以对接CDN厂商作为回源服务器也可以自己搭建集群部署环境。
丰富的内置插件提供了流媒体服务器的常见功能例如rtmp server、http-flv、视频录制、QoS等。除此以外还内置了后台web界面方便观察服务器运行的状态。
也可以自己开发后台管理界面通过api方式获取服务器的运行信息。
Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其功能。</p><h2 id="启动"><a href="#启动" aria-hidden="true" class="header-anchor">#</a> 启动</h2><p>启用所有内置插件</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> main
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token punctuation">.</span> <span class="token string">&quot;github.com/langhuihui/monibuca/monica&quot;</span>
<span class="token boolean">_</span> <span class="token string">&quot;github.com/langhuihui/monibuca/plugins&quot;</span>
<span class="token punctuation">)</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">Run</span><span class="token punctuation">(</span><span class="token string">&quot;config.toml&quot;</span><span class="token punctuation">)</span>
<span class="token keyword">select</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><h2 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h2><p>要使用<code>Monibuca</code>,需要编写一个<code>toml</code>格式的配置文件,通常可以放在程序的同级目录下例如:<code>config.toml</code>(名称不是必须为<code>config</code>)</p><p>该配置文件主要是为了定制各个插件的配置,例如监听端口号等,具体还是要看各个插件的设计。</p><blockquote><p>如果你编写了自己的插件,就必须在该配置文件中写入对自己插件的配置信息</p></blockquote><p>如果注释掉部分插件的配置,那么该插件就不会启用,典型的配置如下:</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:2020&quot;</span>
<span class="token punctuation">[</span><span class="token table class-name">Plugins.Jessica</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:8080&quot;</span>
<span class="token punctuation">[</span><span class="token table class-name">Plugins.RTMP</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:1935&quot;</span>
<span class="token punctuation">[</span><span class="token table class-name">Plugins.GateWay</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:81&quot;</span>
<span class="token comment">#[Plugins.Cluster]</span>
<span class="token comment">#Master = &quot;localhost:2019&quot;</span>
<span class="token comment">#ListenAddr = &quot;:2019&quot;</span>
<span class="token comment">#</span>
<span class="token comment">#[Plugins.Auth]</span>
<span class="token comment">#Key=&quot;www.monibuca.com&quot;</span>
<span class="token comment">#[Plugins.RecordFlv]</span>
<span class="token comment">#Path=&quot;./resouce&quot;</span>
<span class="token punctuation">[</span><span class="token table class-name">Plugins.QoS</span><span class="token punctuation">]</span>
<span class="token key property">Suffix</span> <span class="token punctuation">=</span> <span class="token punctuation">[</span><span class="token string">&quot;high&quot;</span><span class="token punctuation">,</span><span class="token string">&quot;medium&quot;</span><span class="token punctuation">,</span><span class="token string">&quot;low&quot;</span><span class="token punctuation">]</span>
</code></pre></div><p>具体配置的含义,可以参考每个插件的说明</p></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><!----><span class="next"><a href="/docs/develop.html">
插件开发
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/1.6babbc1d.js" defer></script>
</body>
</html>

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>内置插件介绍 | Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/4.08fbc0d9.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js">
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
Monibuca
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="active sidebar-link">内置插件</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/plugins.html#jessica插件" class="sidebar-link">Jessica插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#rtmp插件" class="sidebar-link">Rtmp插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#recordflv插件" class="sidebar-link">RecordFlv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#http-flv插件" class="sidebar-link">Http-Flv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#cluster插件" class="sidebar-link">Cluster插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#hls插件" class="sidebar-link">HLS插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#网关插件" class="sidebar-link">网关插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#校验插件" class="sidebar-link">校验插件</a></li></ul></li></ul></div><div class="page"><div class="content"><h1 id="内置插件介绍"><a href="#内置插件介绍" aria-hidden="true" class="header-anchor">#</a> 内置插件介绍</h1><p>内置插件为Monibuca提供了许多基础功能当然你完全可以不采用内置插件而改用自己开发的插件也丝毫不会影响您使用Monibuca。</p><h2 id="jessica插件"><a href="#jessica插件" aria-hidden="true" class="header-anchor">#</a> Jessica插件</h2><blockquote><p>该插件源码位于plugins/jessica下</p></blockquote><p>该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放
Jessibua播放器已内置于源码中该播放器通过js解码H264/H265并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。</p><h3 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Jessica</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:8080&quot;</span>
</code></pre></div><h3 id="flv格式支持"><a href="#flv格式支持" aria-hidden="true" class="header-anchor">#</a> Flv格式支持</h3><p>Jessica以及Jessibuca也支持采用WebSocket中传输Flv格式的方式进行通讯目前有部分CDN厂商已经支持这种方式进行传输。</p><blockquote><p>私有协议以及Flv格式的判断是通过URL后缀是否带有.flv来进行判断</p></blockquote><h2 id="rtmp插件"><a href="#rtmp插件" aria-hidden="true" class="header-anchor">#</a> Rtmp插件</h2><blockquote><p>该插件源码位于plugins/rtmp下</p></blockquote><p>实现了基本的rtmp传输协议包括接收来自OBS、ffmpeg等软件的推流以及来在Flash Player播放器的拉流。</p><h3 id="配置-2"><a href="#配置-2" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RTMP</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:1935&quot;</span>
</code></pre></div><h2 id="recordflv插件"><a href="#recordflv插件" aria-hidden="true" class="header-anchor">#</a> RecordFlv插件</h2><blockquote><p>该插件源码位于plugins/record下</p></blockquote><p>实现了录制Flv文件的功能并且支持再次使用录制好的Flv文件作为发布者进行发布。在Monibuca的web界面的控制台中提供了对房间进行录制的操作按钮以及列出所有已经录制的文件的界面。</p><h3 id="配置-3"><a href="#配置-3" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>配置中的Path 表示要保存的Flv文件的根路径可以使用相对路径或者绝对路径</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RecordFlv</span><span class="token punctuation">]</span>
<span class="token key property">Path</span><span class="token punctuation">=</span><span class="token string">&quot;./resource&quot;</span>
</code></pre></div><h2 id="http-flv插件"><a href="#http-flv插件" aria-hidden="true" class="header-anchor">#</a> Http-Flv插件</h2><blockquote><p>该插件位于plugins/HDL下</p></blockquote><p>实现了http-flv格式的拉流功能方便对接CDN厂商</p><h3 id="配置-4"><a href="#配置-4" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:2020&quot;</span>
</code></pre></div><h2 id="cluster插件"><a href="#cluster插件" aria-hidden="true" class="header-anchor">#</a> Cluster插件</h2><blockquote><p>该插件源码位于plugins/cluster下</p></blockquote><p>实现了基本的集群功能,里面包含一对发布者和订阅者,分别在主从服务器中启用,进行连接。
起基本原理就是在主服务器启动端口监听从服务器收到播放请求时如果从服务器没有对应的发布者则向主服务器发起请求主服务器收到来自从服务器的请求时将该请求作为一个订阅者。从服务器则把tcp连接作为发布者实现视频流的传递过程。</p><h3 id="配置-5"><a href="#配置-5" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>主服务器的配置是ListenAddr用来监听从服务器的请求。
从服务器的配置是Master,表示主服务器的地址。
当然服务器可以既是主也是从,即充当中转站。</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Cluster</span><span class="token punctuation">]</span>
<span class="token key property">Master</span> <span class="token punctuation">=</span> <span class="token string">&quot;localhost:2019&quot;</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:2019&quot;</span>
</code></pre></div><h2 id="hls插件"><a href="#hls插件" aria-hidden="true" class="header-anchor">#</a> HLS插件</h2><blockquote><p>该插件源码位于plugins/HLS下</p></blockquote><p>该插件的作用是请求M3u8文件进行解码最终将TS视频流转码成裸的视频流进行发布。
注意该插件目前并没有实现生成HLS的功能。</p><h2 id="网关插件"><a href="#网关插件" aria-hidden="true" class="header-anchor">#</a> 网关插件</h2><blockquote><p>该插件位于plugins/gateway下</p></blockquote><p>该插件是为web控制台界面提供api用来采集服务器的信息。</p><h3 id="配置-6"><a href="#配置-6" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.GateWay</span><span class="token punctuation">]</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:80&quot;</span>
</code></pre></div><p>如果80端口有其他用途可以换成别的端口比如有nginx反向代理。</p><h2 id="校验插件"><a href="#校验插件" aria-hidden="true" class="header-anchor">#</a> 校验插件</h2><blockquote><p>该插件位于plugins/auth下</p></blockquote><p>该插件提供了基本的验证功能,其原理是
订阅流提供一个签名签名只可以使用一次把签名进行AES CBC 解密如果得到的解密字符串的前面部分就是和Key相同则通过验证。</p><h3 id="配置-7"><a href="#配置-7" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>Key代表用来加密的Key</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Auth</span><span class="token punctuation">]</span>
<span class="token key property">Key</span><span class="token punctuation">=</span><span class="token string">&quot;www.monibuca.com&quot;</span>
</code></pre></div></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
<a href="/docs/history.html" class="prev">
更新日志
</a></span><!----></p></div></div></div></div>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/4.08fbc0d9.js" defer></script>
</body>
</html>

View File

@@ -1,72 +0,0 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/
importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [
{
"url": "404.html",
"revision": "75925fb89348802ecd737cdad2d5d801"
},
{
"url": "assets/css/styles.d51c815b.css",
"revision": "4a6b650244e5b709f84a81ad0565b485"
},
{
"url": "assets/img/search.83621669.svg",
"revision": "83621669651b9a3d4bf64d1a670ad856"
},
{
"url": "assets/js/1.6babbc1d.js",
"revision": "0142763a4e8630af56b66f5fa8320c5c"
},
{
"url": "assets/js/2.190ec46a.js",
"revision": "8dceb01ded85f36cc30e0e18371fe5d4"
},
{
"url": "assets/js/3.7646e76c.js",
"revision": "378c41587710afd31466293c83a6c738"
},
{
"url": "assets/js/4.08fbc0d9.js",
"revision": "0ea387538f5b25ef46237e3dcc1c1694"
},
{
"url": "assets/js/app.d51c815b.js",
"revision": "467373b055c3575c2082a1b9ab1769fd"
},
{
"url": "develop.html",
"revision": "92b13eb27581e4dc7008cf4205e5c215"
},
{
"url": "history.html",
"revision": "ef202ac3bf63e1bdef479a05d49d5a8d"
},
{
"url": "index.html",
"revision": "306f1a0a8ceadb0c85dae2342f0bd637"
},
{
"url": "plugins.html",
"revision": "ac2ce38679fb75f05e8e501f35d161bd"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.ea4656d8.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.af5e5ef3.js rel=preload as=script><link href=/js/chunk-vendors.ebc28a73.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.ea4656d8.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ebc28a73.js></script><script src=/js/app.af5e5ef3.js></script></body></html>

View File

@@ -1,535 +0,0 @@
// a simple ajax
!(function () {
var jsonType = 'application/json';
var htmlType = 'text/html';
var xmlTypeRE = /^(?:text|application)\/xml/i;
var blankRE = /^\s*$/; // \s
/*
* default setting
* */
var _settings = {
type: "GET",
beforeSend: noop,
success: noop,
error: noop,
complete: noop,
context: null,
xhr: function () {
return new window.XMLHttpRequest();
},
accepts: {
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
crossDomain: false,
timeout: 0,
username: null,
password: null,
processData: true,
promise: noop
};
function noop() {
}
var ajax = function (options) {
//
var settings = extend({}, options || {});
//
for (var key in _settings) {
if (settings[key] === undefined) {
settings[key] = _settings[key];
}
}
//
try {
var q = {};
var promise = new Promise(function (resolve, reject) {
q.resolve = resolve;
q.reject = reject;
});
promise.resolve = q.resolve;
promise.reject = q.reject;
settings.promise = promise;
}
catch (e) {
//
settings.promise = {
resolve: noop,
reject: noop
};
}
//
if (!settings.crossDomain) {
settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 !== window.location.href;
}
var dataType = settings.dataType;
// jsonp
if (dataType === 'jsonp') {
//
var hasPlaceholder = /=\?/.test(settings.url);
if (!hasPlaceholder) {
var jsonpCallback = (settings.jsonp || 'callback') + '=?';
settings.url = appendQuery(settings.url, jsonpCallback)
}
return JSONP(settings);
}
// url
if (!settings.url) {
settings.url = window.location.toString();
}
//
serializeData(settings);
var mime = settings.accepts[dataType]; // mime
var baseHeader = {}; // header
var protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol; // protocol
var xhr = _settings.xhr();
var abortTimeout;
// X-Requested-With header
// For cross-domain requests, seeing as conditions for a preflight are
// akin to a jigsaw puzzle, we simply never set it to be sure.
// (it can always be set on a per-request basis or even using ajaxSetup)
// For same-domain requests, won't change header if already provided.
if (!settings.crossDomain && !baseHeader['X-Requested-With']) {
baseHeader['X-Requested-With'] = 'XMLHttpRequest';
}
// mime
if (mime) {
//
baseHeader['Accept'] = mime;
if (mime.indexOf(',') > -1) {
mime = mime.split(',', 2)[0]
}
//
xhr.overrideMimeType && xhr.overrideMimeType(mime);
}
// contentType
if (settings.contentType || (settings.data && settings.type.toUpperCase() !== 'GET')) {
baseHeader['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded; charset=UTF-8');
}
// headers
settings.headers = extend(baseHeader, settings.headers || {});
// on ready state change
xhr.onreadystatechange = function () {
// readystate
if (xhr.readyState === 4) {
clearTimeout(abortTimeout);
var result;
var error = false;
//
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'));
result = xhr.responseText;
try {
// xml
if (dataType === 'xml') {
result = xhr.responseXML;
}
// json
else if (dataType === 'json') {
result = blankRE.test(result) ? null : JSON.parse(result);
}
}
catch (e) {
error = e;
}
if (error) {
ajaxError(error, 'parseerror', xhr, settings);
}
else {
ajaxSuccess(result, xhr, settings);
}
}
else {
ajaxError(null, 'error', xhr, settings);
}
}
};
// async
var async = 'async' in settings ? settings.async : true;
// open
xhr.open(settings.type, settings.url, async, settings.username, settings.password);
// xhrFields
if (settings.xhrFields) {
for (var name in settings.xhrFields) {
xhr[name] = settings.xhrFields[name];
}
}
// Override mime type if needed
if (settings.mimeType && xhr.overrideMimeType) {
xhr.overrideMimeType(settings.mimeType);
}
// set request header
for (var name in settings.headers) {
// Support: IE<9
// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
// request header to a null-value.
//
// To keep consistent with other XHR implementations, cast the value
// to string and ignore `undefined`.
if (settings.headers[name] !== undefined) {
xhr.setRequestHeader(name, settings.headers[name] + "");
}
}
// before send
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort();
return false;
}
// timeout
if (settings.timeout > 0) {
abortTimeout = window.setTimeout(function () {
xhr.onreadystatechange = noop;
xhr.abort();
ajaxError(null, 'timeout', xhr, settings);
}, settings.timeout);
}
// send
xhr.send(settings.data ? settings.data : null);
return settings.promise;
};
/*
* method get
* */
ajax.get = function (url, data, success, dataType) {
if (isFunction(data)) {
dataType = dataType || success;
success = data;
data = undefined;
}
return ajax({
url: url,
data: data,
success: success,
dataType: dataType
});
};
/*
* method post
*
* dataType:
* */
ajax.post = function (url, data, success, dataType) {
if (isFunction(data)) {
dataType = dataType || success;
success = data;
data = undefined;
}
return ajax({
type: 'POST',
url: url,
data: data,
success: success,
dataType: dataType
})
};
/*
* method getJSON
* */
ajax.getJSON = function (url, data, success) {
if (isFunction(data)) {
success = data;
data = undefined;
}
return ajax({
url: url,
data: data,
success: success,
dataType: 'json'
})
};
/*
* method ajaxSetup
* */
ajax.ajaxSetup = function (target, settings) {
return settings ? extend(extend(target, _settings), settings) : extend(_settings, target);
};
/*
* utils
*
* */
// triggers and extra global event ajaxBeforeSend that's like ajaxSend but cancelable
function ajaxBeforeSend(xhr, settings) {
var context = settings.context;
//
if (settings.beforeSend.call(context, xhr, settings) === false) {
return false;
}
}
// ajax success
function ajaxSuccess(data, xhr, settings) {
var context = settings.context;
var status = 'success';
settings.success.call(context, data, status, xhr);
settings.promise.resolve(data, status, xhr);
ajaxComplete(status, xhr, settings);
}
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
function ajaxComplete(status, xhr, settings) {
var context = settings.context;
settings.complete.call(context, xhr, status);
}
// type: "timeout", "error", "abort", "parsererror"
function ajaxError(error, type, xhr, settings) {
var context = settings.context;
settings.error.call(context, xhr, type, error);
settings.promise.reject(xhr, type, error);
ajaxComplete(type, xhr, settings);
}
// jsonp
/*
* tks: https://www.cnblogs.com/rubylouvre/archive/2011/02/13/1953087.html
* */
function JSONP(options) {
//
var callbackName = options.jsonpCallback || 'jsonp' + (new Date().getTime());
var script = window.document.createElement('script');
var abort = function () {
// 设置 window.xxx = noop
if (callbackName in window) {
window[callbackName] = noop;
}
};
var xhr = {abort: abort};
var abortTimeout;
var head = window.document.getElementsByTagName('head')[0] || window.document.documentElement;
// ie8+
script.onerror = function (error) {
_error(error);
};
function _error(error) {
window.clearTimeout(abortTimeout);
xhr.abort();
ajaxError(error.type, xhr, error.type, options);
_removeScript();
}
window[callbackName] = function (data) {
window.clearTimeout(abortTimeout);
ajaxSuccess(data, xhr, options);
_removeScript();
};
//
serializeData(options);
script.src = options.url.replace(/=\?/, '=' + callbackName);
//
script.src = appendQuery(script.src, '_=' + (new Date()).getTime());
//
script.async = true;
// script charset
if (options.scriptCharset) {
script.charset = options.scriptCharset;
}
//
head.insertBefore(script, head.firstChild);
//
if (options.timeout > 0) {
abortTimeout = window.setTimeout(function () {
xhr.abort();
ajaxError('timeout', xhr, 'timeout', options);
_removeScript();
}, options.timeout);
}
// remove script
function _removeScript() {
if (script.clearAttributes) {
script.clearAttributes();
} else {
script.onload = script.onreadystatechange = script.onerror = null;
}
if (script.parentNode) {
script.parentNode.removeChild(script);
}
//
script = null;
delete window[callbackName];
}
return options.promise;
}
// mime to data type
function mimeToDataType(mime) {
return mime && (mime === htmlType ? 'html' : mime === jsonType ? 'json' : xmlTypeRE.test(mime) && 'xml') || 'text'
}
// append query
function appendQuery(url, query) {
return (url + '&' + query).replace(/[&?]{1,2}/, '?');
}
// serialize data
function serializeData(options) {
// formData
if (isObject(options) && !isFormData(options.data) && options.processData) {
options.data = param(options.data);
}
if (options.data && (!options.type || options.type.toUpperCase() === 'GET')) {
options.url = appendQuery(options.url, options.data);
}
}
// serialize
function serialize(params, obj, traditional, scope) {
var _isArray = isArray(obj);
for (var key in obj) {
var value = obj[key];
if (scope) {
key = traditional ? scope : scope + '[' + (_isArray ? '' : key) + ']';
}
// handle data in serializeArray format
if (!scope && _isArray) {
params.add(value.name, value.value);
}
else if (traditional ? _isArray(value) : isObject(value)) {
serialize(params, value, traditional, key);
}
else {
params.add(key, value);
}
}
}
// param
function param(obj, traditional) {
var params = [];
//
params.add = function (k, v) {
this.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
};
serialize(params, obj, traditional);
return params.join('&').replace('%20', '+');
}
// extend
function extend(target) {
var slice = Array.prototype.slice;
var args = slice.call(arguments, 1);
//
for (var i = 0, length = args.length; i < length; i++) {
var source = args[i] || {};
for (var key in source) {
if (source.hasOwnProperty(key) && source[key] !== undefined) {
target[key] = source[key];
}
}
}
return target;
}
// is object
function isObject(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}
// is formData
function isFormData(obj) {
return obj instanceof FormData;
}
// is array
function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}
// is function
function isFunction(value) {
return typeof value === "function";
}
// browser
window.ajax = ajax;
})();

File diff suppressed because one or more lines are too long

View File

@@ -1,460 +0,0 @@
function Jessibuca(opt) {
this.canvasElement = opt.canvas;
this.contextOptions = opt.contextOptions;
this.videoBuffer = opt.videoBuffer || 1
if (!opt.forceNoGL) this.initContextGL();
if (this.contextGL) {
this.initProgram();
this.initBuffers();
this.initTextures();
};
this.decoderWorker = new Worker(opt.decoder || '264_mp3.js')
var _this = this
function draw(output) {
_this.drawNextOutputPicture(_this.width, _this.height, null, output)
postMessage({ cmd: "setBuffer", buffer: output }, '*', [output[0].buffer, output[1].buffer, output[2].buffer])
}
this.decoderWorker.onmessage = function (event) {
var msg = event.data
switch (msg.cmd) {
case "init":
console.log("decoder worker init")
postMessage({ cmd: "setVideoBuffer", time: _this.videoBuffer }, "*")
if (_this.onLoad) {
_this.onLoad()
delete _this.onLoad;
}
break
case "initSize":
_this.width = msg.w
_this.height = msg.h
if (_this.isWebGL()) {
// var buffer = new ArrayBuffer(msg.w * msg.h + (msg.w * msg.h >> 1))
// this.postMessage({ cmd: "setBuffer", buffer: buffer }, [buffer])
}
else {
_this.initRGB(msg.w, msg.h)
}
break
case "render":
if (_this.onPlay) {
_this.onPlay()
delete _this.onPlay;
}
// if (msg.compositionTime) {
// console.log(msg.compositionTime)
// setTimeout(draw, msg.compositionTime, msg.output)
// } else {
// draw(msg.output)
// }
draw(msg.output)
break
case "initAudio":
_this.initAudioPlay(msg.frameCount, msg.samplerate, msg.channels)
break
case "playAudio":
_this.playAudio(msg.buffer)
break
case "print":
console.log(msg.text);
break
case "printErr":
console.error(msg.text);
break
}
}
};
window.AudioContext = window.AudioContext || window.webkitAudioContext;
function _unlock() {
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
context.resume();
var source = context.createBufferSource();
source.buffer = context.createBuffer(1, 1, 22050);
source.connect(context.destination);
if (source.noteOn)
source.noteOn(0);
else
source.start(0);
}
// document.addEventListener("mousedown", _unlock, true);
// document.addEventListener("touchend", _unlock, true);
Jessibuca.prototype.audioEnabled = function (flag) {
if (flag) {
_unlock()
this.audioEnabled = function (flag) {
if (flag) {
this.audioContext.resume();
} else {
this.audioContext.suspend();
}
}
}
}
Jessibuca.prototype.playAudio = function (data) {
var context = this.audioContext;
var isPlaying = false;
var isDecoding = false;
if (!context) return false;
var audioBuffers = [];
var decodeQueue = []
var _this = this
var playNextBuffer = function (e) {
// isPlaying = false;
if (audioBuffers.length) {
playBuffer(audioBuffers.shift())
}
//if (audioBuffers.length > 1) audioBuffers.shift();
};
var playBuffer = function (buffer) {
isPlaying = true;
var audioBufferSouceNode = context.createBufferSource();
audioBufferSouceNode.buffer = buffer;
audioBufferSouceNode.connect(context.destination);
// audioBufferSouceNode.onended = playNextBuffer;
audioBufferSouceNode.start();
if (!_this.audioInterval) {
_this.audioInterval = setInterval(playNextBuffer, buffer.duration * 1000 - 1);
}
// setTimeout(playNextBuffer, buffer.duration * 1000)
}
var tryPlay = function (buffer) {
if (decodeQueue.length) {
context.decodeAudioData(decodeQueue.shift(), tryPlay, console.error);
} else {
isDecoding = false
}
if (isPlaying) {
audioBuffers.push(buffer);
} else {
playBuffer(buffer)
}
}
var playAudio = function (data) {
decodeQueue.push(...data)
if (!isDecoding) {
isDecoding = true
context.decodeAudioData(decodeQueue.shift(), tryPlay, console.error);
}
}
this.playAudio = playAudio
playAudio(data)
}
Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels) {
var context = this.audioContext;
var isPlaying = false;
var audioBuffers = [];
if (!context) return false;
var resampled = samplerate < 22050;
var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate);
var playNextBuffer = function () {
isPlaying = false;
console.log("~", audioBuffers.length)
if (audioBuffers.length) {
playAudio(audioBuffers.shift());
}
//if (audioBuffers.length > 1) audioBuffers.shift();
};
var copyToCtxBuffer = channels > 1 ? function (fromBuffer) {
for (var channel = 0; channel < channels; channel++) {
var nowBuffering = audioBuffer.getChannelData(channel);
if (resampled) {
for (var i = 0; i < frameCount; i++) {
nowBuffering[i * 2] = nowBuffering[i * 2 + 1] = fromBuffer[i * (channel + 1)] / 32768;
}
} else
for (var i = 0; i < frameCount; i++) {
nowBuffering[i] = fromBuffer[i * (channel + 1)] / 32768;
}
}
} : function (fromBuffer) {
var nowBuffering = audioBuffer.getChannelData(0);
for (var i = 0; i < nowBuffering.length; i++) {
nowBuffering[i] = fromBuffer[i] / 32768;
}
// nowBuffering.set(fromBuffer);
};
var playAudio = function (fromBuffer) {
if (isPlaying) {
audioBuffers.push(fromBuffer);
console.log(audioBuffers.length)
return;
}
isPlaying = true;
copyToCtxBuffer(fromBuffer);
var source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.onended = playNextBuffer;
//setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
source.start();
};
this.playAudio = playAudio;
}
/**
* Returns true if the canvas supports WebGL
*/
Jessibuca.prototype.isWebGL = function () {
return !!this.contextGL;
};
/**
* Create the GL context from the canvas element
*/
Jessibuca.prototype.initContextGL = function () {
var canvas = this.canvasElement;
var gl = null;
var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
var nameIndex = 0;
while (!gl && nameIndex < validContextNames.length) {
var contextName = validContextNames[nameIndex];
try {
if (this.contextOptions) {
gl = canvas.getContext(contextName, this.contextOptions);
} else {
gl = canvas.getContext(contextName);
};
} catch (e) {
gl = null;
}
if (!gl || typeof gl.getParameter !== "function") {
gl = null;
}
++nameIndex;
};
this.contextGL = gl;
};
/**
* Initialize GL shader program
*/
Jessibuca.prototype.initProgram = function () {
var gl = this.contextGL;
var vertexShaderScript = [
'attribute vec4 vertexPos;',
'attribute vec4 texturePos;',
'varying vec2 textureCoord;',
'void main()',
'{',
'gl_Position = vertexPos;',
'textureCoord = texturePos.xy;',
'}'
].join('\n');
var fragmentShaderScript = [
'precision highp float;',
'varying highp vec2 textureCoord;',
'uniform sampler2D ySampler;',
'uniform sampler2D uSampler;',
'uniform sampler2D vSampler;',
'const mat4 YUV2RGB = mat4',
'(',
'1.1643828125, 0, 1.59602734375, -.87078515625,',
'1.1643828125, -.39176171875, -.81296875, .52959375,',
'1.1643828125, 2.017234375, 0, -1.081390625,',
'0, 0, 0, 1',
');',
'void main(void) {',
'highp float y = texture2D(ySampler, textureCoord).r;',
'highp float u = texture2D(uSampler, textureCoord).r;',
'highp float v = texture2D(vSampler, textureCoord).r;',
'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
'}'
].join('\n');
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderScript);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
}
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderScript);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
}
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
}
gl.useProgram(program);
this.shaderProgram = program;
};
/**
* Initialize vertex buffers and attach to shader program
*/
Jessibuca.prototype.initBuffers = function () {
var gl = this.contextGL;
var program = this.shaderProgram;
var vertexPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
gl.enableVertexAttribArray(vertexPosRef);
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
var texturePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
var texturePosRef = gl.getAttribLocation(program, 'texturePos');
gl.enableVertexAttribArray(texturePosRef);
gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
this.texturePosBuffer = texturePosBuffer;
};
/**
* Initialize GL textures and attach to shader program
*/
Jessibuca.prototype.initTextures = function () {
var gl = this.contextGL;
var program = this.shaderProgram;
var yTextureRef = this.initTexture();
var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
gl.uniform1i(ySamplerRef, 0);
this.yTextureRef = yTextureRef;
var uTextureRef = this.initTexture();
var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
gl.uniform1i(uSamplerRef, 1);
this.uTextureRef = uTextureRef;
var vTextureRef = this.initTexture();
var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
gl.uniform1i(vSamplerRef, 2);
this.vTextureRef = vTextureRef;
};
/**
* Create and configure a single texture
*/
Jessibuca.prototype.initTexture = function () {
var gl = this.contextGL;
var textureRef = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textureRef);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return textureRef;
};
/**
* Draw picture data to the canvas.
* If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
* Otherwise, data must be an RGBA formatted ArrayBuffer.
*/
Jessibuca.prototype.drawNextOutputPicture = function (width, height, croppingParams, data) {
var gl = this.contextGL;
if (gl) {
this.drawNextOuptutPictureGL(width, height, croppingParams, data);
} else {
this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
}
};
/**
* Draw the next output picture using WebGL
*/
Jessibuca.prototype.drawNextOuptutPictureGL = function (width, height, croppingParams, data) {
var gl = this.contextGL;
var texturePosBuffer = this.texturePosBuffer;
var yTextureRef = this.yTextureRef;
var uTextureRef = this.uTextureRef;
var vTextureRef = this.vTextureRef;
this.contextGL.viewport(0, 0, this.canvasElement.width, this.canvasElement.height);
// if (!croppingParams) {
// gl.viewport(0, 0, width, height);
// } else {
// gl.viewport(0, 0, croppingParams.width, croppingParams.height);
// var tTop = croppingParams.top / height;
// var tLeft = croppingParams.left / width;
// var tBottom = croppingParams.height / height;
// var tRight = croppingParams.width / width;
// var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
// gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
// gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
// }
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[0]);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[1]);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[2]);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
/**
* Draw next output picture using ARGB data on a 2d canvas.
*/
Jessibuca.prototype.drawNextOuptutPictureRGBA = function (width, height, croppingParams, data) {
// var canvas = this.canvasElement;
//var argbData = data;
//var ctx = canvas.getContext('2d');
// var imageData = ctx.getImageData(0, 0, width, height);
//this.imageData = this.ctx2d.getImageData(0, 0, width, height);
this.imageData.data.set(data);
//Module.print(typeof this.imageData.data);
if (!croppingParams) {
this.ctx2d.putImageData(this.imageData, 0, 0);
} else {
this.ctx2d.putImageData(this.imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
}
};
Jessibuca.prototype.ctx2d = null;
Jessibuca.prototype.imageData = null;
Jessibuca.prototype.initRGB = function (width, height) {
this.ctx2d = this.canvasElement.getContext('2d');
this.imageData = this.ctx2d.getImageData(0, 0, width, height);
this.clear = function () {
this.ctx2d.clearRect(0, 0, width, height)
};
//Module.print(this.imageData);
};
Jessibuca.prototype.close = function () {
if (this.audioInterval) {
clearInterval(this.audioInterval)
}
this.decoderWorker.postMessage({ cmd: "close" })
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
}
Jessibuca.prototype.destroy = function(){
this.decoderWorker.terminate()
}
Jessibuca.prototype.play = function (url) {
this.decoderWorker.postMessage({ cmd: "play", url: url, isWebGL: this.isWebGL() })
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +0,0 @@
module.exports = {
dest: 'public/docs',
serviceWorker: true,
themeConfig: {
sidebar: [
['/', '起步'],
['/develop', '插件开发'],
['/history', '更新日志'],
['/plugins', '内置插件']
]
},
title: 'Monibuca',
base: '/docs/'
}

View File

@@ -1,38 +0,0 @@
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
export default ({
Vue, // the version of Vue being used in the VuePress app
options, // the options for the root Vue instance
router, // the router instance for the app
siteData // site metadata
}) => {
// ...apply enhancements to the app
const requireComponent = require.context(
// The relative path of the components folder
'../../src/components',
// Whether or not to look in subfolders
true,
// The regular expression used to match base component filenames
/.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// Get component config
const componentConfig = requireComponent(fileName)
const fc = fileName.split('/')
const f = fc[fc.length - 1]
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(
f.replace(/.*\//, '$1').replace(/\.\w+$/,'')
)
)
// Register component globally
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
}

View File

@@ -1,53 +0,0 @@
# Monibuca快速起步
## 介绍
Monibuca 是一个开源的流媒体服务器开发框架适用于快速定制化开发流媒体服务器可以对接CDN厂商作为回源服务器也可以自己搭建集群部署环境。
丰富的内置插件提供了流媒体服务器的常见功能例如rtmp server、http-flv、视频录制、QoS等。除此以外还内置了后台web界面方便观察服务器运行的状态。
也可以自己开发后台管理界面通过api方式获取服务器的运行信息。
Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其功能。
## 启动
启用所有内置插件
```go
package main
import (
. "github.com/langhuihui/monibuca/monica"
_ "github.com/langhuihui/monibuca/plugins"
)
func main() {
Run("config.toml")
select {}
}
```
## 配置
要使用`Monibuca`,需要编写一个`toml`格式的配置文件,通常可以放在程序的同级目录下例如:`config.toml`(名称不是必须为`config`)
该配置文件主要是为了定制各个插件的配置,例如监听端口号等,具体还是要看各个插件的设计。
> 如果你编写了自己的插件,就必须在该配置文件中写入对自己插件的配置信息
如果注释掉部分插件的配置,那么该插件就不会启用,典型的配置如下:
```toml
[Plugins.HDL]
ListenAddr = ":2020"
[Plugins.Jessica]
ListenAddr = ":8080"
[Plugins.RTMP]
ListenAddr = ":1935"
[Plugins.GateWay]
ListenAddr = ":81"
#[Plugins.Cluster]
#Master = "localhost:2019"
#ListenAddr = ":2019"
#
#[Plugins.Auth]
#Key="www.monibuca.com"
#[Plugins.RecordFlv]
#Path="./resouce"
[Plugins.QoS]
Suffix = ["high","medium","low"]
```
具体配置的含义,可以参考每个插件的说明

View File

@@ -1,43 +0,0 @@
# Monibuca设计思想
## 背景
市面上的流媒体服务器不可谓不多,从本人的第一份工作起,就一直接触和研究了形形色色的流媒体服务器,从最早的**FCS**(全称Flash Communication Server),后来改名为**FMS**(全称Flash Media Server),到Red5(java语言开发),到**CrtmpServer**(C++开发)让我对流媒体服务器的基本原理有了深刻的认识。当时本人痴迷C#于是乎在业余时间对crtmpServer的代码进行移植用C#仿照着写了一遍取名为[csharprtmp](https://github.com/langhuihui/csharprtmp)并且适当的增强了一些功能于是对rtmp协议了如指掌。后来Adobe推出了RTMFP协议是一种p2p协议十分节省带宽。我就又开始研究一款名为**OpenRTMFP**的开源项目,后来该项目改名为**MonaServer**。我在起基础上进行了扩展实现了一些例如录制flvshareObject等原本FMS有的功能。后开发出了HTML5直播技术现在命名为Jessibuca,尚未开源)采用的传输协议就是WebSocket传输裸的视频流的方式属于私有协议。而Server当时就使用的MonaServer。但当时遇到一个问题C++的内存泄漏问题这个一直没有很好的解决。遂决定放弃使用MonaServer转而使用srs而srs要用一个很简单的go写的小程序将http-flv转换成WebSocket的Flv来适配我的Jessibuca感觉最好能直接修改srs来实现这个功能。对srs的源码研究了一小段时间后放弃了因为C++代码过于难写容易出现bug。后来转而使用golang写的**gortmp**作为server同样对其进行了扩展而且进展十分顺利golang的开发效率令人惊叹而且其协程的特性很完美的处理了流媒体服务器的并发的场景。所以使用golang写的流媒体服务器项目很多github上随便一搜就有很多比如**livego**、**joy4**等。期间还接触到一位使用Node.js实现的流媒体服务器Node Media Server我也和作者交流了许多收益良多。
### 现有项目的不足
虽然流媒体服务器项目很多,但在我使用过程中遇到了几个痛点
1. 功能太多太重,往往大而全,不够轻量 很多号称轻量的项目最后都会越来越重
2. 扩展性弱由于功能复杂设计之初没有提供良好的扩展性有些项目带有脚本支持如FMS和MonaServer但执行脚本会牺牲性能而且脚本和原生代码相比功能限制很大只能实现业务逻辑而不是流媒体服务器本身的功能扩展。
3. 缺少图形管理界面FMS是配套有图形管理界面的当然FMS的问题是商业软件需要付费源码也是不可见的。
综上所述本人在吸收了以上诸多流媒体服务器的设计后完成了Monibuca这款golang编写的流媒体开发框架的编写
### 受到vue渐进式思想的影响
vue渐进式框架的设计思想非常棒那么是否可以用来设计流媒体服务器使得流媒体服务器不只是一个服务器而是一个开发框架让开发者可以定制化自己的流媒体服务器呢答案是肯定的。当然我们需要更多的抽象。
## 流媒体服务器的核心
流媒体服务器的核心是**转发**二字。当你去研究一款流媒体服务器的时候,会有海量的代码阻碍你看清其核心逻辑。包括:
1. 多媒体格式定义、解析如Flv、MP4、MP3、H264、AAC等等
2. 传输协议的解析如RTMP家族、AMF、HTTP、RTSP、HLS、WebSocket等等
3. 各种工具类,用来读取字节的缓冲、大小端转换、加解密算法、等等
大部分流媒体服务器都是基于rtmp协议之上扩展而来这是历史原因造成的所以功能不能很好的分离耦合度很高。往往牵一发而动全身。其实所谓的流媒体服务器本质上就是把发布者的数据经过服务器转发到订阅者手里播放起一个中转作用。至于什么协议格式什么媒体格式都是属于扩展功能。所以最轻量的服务器应该不包含任何协议格式任何媒体格式仅仅只是完成中转。再说的直白一点核心代码就是一个for循环。
```go
for _, v := range r.Subscribers {
v.sendVideo(video)
}
```
其他都是围绕这个for循环展开。所有的流媒体服务器代码里面都有这个for循环写法稍有不同但本质相同。
### 核心概念
基于这个循环,我们需要思考两个问题:
1. 如何高效的循环(性能问题)
2. 如何对接不同的协议(扩展性)
第一个问题golang的性能算是很好的那么重点就在于减少内存的分配上池化是一个不错的选择所以尽量池化在Monibuca中对`[]byte`类型,采用了[github.com/funny/slab](https://github.com/funny/slab)包来管理。其他结构体就用系统自带的pool包来池化对象。对于协程的使用在多次迭代后已经使用了最少的协程来支持并发性。

View File

@@ -1,204 +0,0 @@
# 插件开发
## 插件的定义
所谓的插件,没有什么固定的规则,只需要完成`安装`操作即可。插件可以实现任意的功能扩展,最常见的是实现某种传输协议用来推流或者拉流
## 插件的安装
下面是内置插件jessica的源码代表了典型的插件安装
```go
package jessica
import (
. "github.com/langhuihui/monibuca/monica"
"log"
"net/http"
)
var config = new(ListenerConfig)
func init() {
InstallPlugin(&PluginConfig{
Name: "Jessica",
Type: PLUGIN_SUBSCRIBER,
Config: config,
Run: run,
})
}
func run() {
log.Printf("server Jessica start at %s", config.ListenAddr)
log.Fatal(http.ListenAndServe(config.ListenAddr, http.HandlerFunc(WsHandler)))
}
```
当主程序读取配置文件完成解析后会调用各个插件的Run函数上面代码中执行了一个http的端口监听
## 开发订阅者插件
所谓订阅者就是用来从流媒体服务器接收音视频流的程序例如RTMP协议执行play命令后、http-flv请求响应程序、websocket响应程序。内置插件中录制flv程序也是一个特殊的订阅者。
下面是http-flv插件的源码供参考
```go
package HDL
import (
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/avformat"
"github.com/langhuihui/monibuca/monica/pool"
"log"
"net/http"
"strings"
)
var config = new(ListenerConfig)
func init() {
InstallPlugin(&PluginConfig{
Name: "HDL",
Type: PLUGIN_SUBSCRIBER,
Config: config,
Run: run,
})
}
func run() {
log.Printf("HDL start at %s", config.ListenAddr)
log.Fatal(http.ListenAndServe(config.ListenAddr, http.HandlerFunc(HDLHandler)))
}
func HDLHandler(w http.ResponseWriter, r *http.Request) {
sign := r.URL.Query().Get("sign")
if err := AuthHooks.Trigger(sign); err != nil {
w.WriteHeader(403)
return
}
stringPath := strings.TrimLeft(r.RequestURI, "/")
if strings.HasSuffix(stringPath, ".flv") {
stringPath = strings.TrimRight(stringPath, ".flv")
}
if _, ok := AllRoom.Load(stringPath); ok {
//atomic.AddInt32(&hdlId, 1)
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Content-Type", "video/x-flv")
w.Write(avformat.FLVHeader)
p := OutputStream{
Sign: sign,
SendHandler: func(packet *pool.SendPacket) error {
return avformat.WriteFLVTag(w, packet)
},
SubscriberInfo: SubscriberInfo{
ID: r.RemoteAddr, Type: "FLV",
},
}
p.Play(stringPath)
} else {
w.WriteHeader(404)
}
}
```
其中核心逻辑就是创建OutputStream对象每一个订阅者需要提供SendHandler函数用来接收来自发布者广播出来的音视频数据。
最后调用该对象的Play函数进行播放。请注意Play函数会阻塞当前goroutine。
## 开发发布者插件
所谓发布者就是提供音视频数据的程序例如接收来自OBS、ffmpeg的推流的程序。内置插件中集群功能里面有一个特殊的发布者它接收来自源服务器的音视频数据然后在本服务器中广播音视频。
以此为例,我们需要提供一个结构体定义来表示特定的发布者:
```go
type Receiver struct {
InputStream
io.Reader
*bufio.Writer
}
```
其中InputStream 是固定的,必须包含,且必须以组合继承的方式定义。其余的成员则是任意的。
发布者的发布动作需要特定条件的触发,例如在集群插件中,当本服务器有订阅者订阅了某个流,而该流并没有发布者的时候就会触发向源服务器拉流的函数:
```go
func PullUpStream(streamPath string) {
addr, err := net.ResolveTCPAddr("tcp", config.Master)
if MayBeError(err) {
return
}
conn, err := net.DialTCP("tcp", nil, addr)
if MayBeError(err) {
return
}
brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
p := &Receiver{
Reader: conn,
Writer: brw.Writer,
}
if p.Publish(streamPath, p) {
p.WriteByte(MSG_SUBSCRIBE)
p.WriteString(streamPath)
p.WriteByte(0)
p.Flush()
for _, v := range p.Subscribers {
p.Auth(v)
}
} else {
return
}
defer p.Cancel()
for {
cmd, err := brw.ReadByte()
if MayBeError(err) {
return
}
switch cmd {
case MSG_AUDIO:
if audio, err := p.readAVPacket(avformat.FLV_TAG_TYPE_AUDIO); err == nil {
p.PushAudio(audio)
}
case MSG_VIDEO:
if video, err := p.readAVPacket(avformat.FLV_TAG_TYPE_VIDEO); err == nil && len(video.Payload) > 2 {
tmp := video.Payload[0] // 第一个字节保存着视频的相关信息.
video.VideoFrameType = tmp >> 4 // 帧类型 4Bit, H264一般为1或者2
p.PushVideo(video)
}
case MSG_AUTH:
cmd, err = brw.ReadByte()
if MayBeError(err) {
return
}
bytes, err := brw.ReadBytes(0)
if MayBeError(err) {
return
}
subId := strings.Split(string(bytes[0:len(bytes)-1]), ",")[0]
if v, ok := p.Subscribers[subId]; ok {
if cmd != 1 {
v.Cancel()
}
}
}
}
}
```
正在该函数中会向源服务器建立tcp连接然后发送特定命令表示需要拉流当我们接收到源服务器的数据的时候就调用PushVideo和PushAudio函数来广播音视频。
核心逻辑是调用InputStream的Publish以及PushVideo、PushAudio函数
## 开发钩子插件
钩子插件就是在服务器的关键逻辑处插入的函数调用,方便扩展服务器的功能,比如对连接进行验证,或者触发一些特殊的发布者。
目前提供的钩子包括
- 当发布者开始发布时 `OnPublishHooks.AddHook(onPublish)`
例如:
```go
func onPublish(r *Room) {
for _, v := range r.Subscribers {
if err := CheckSign(v.Sign); err != nil {
v.Cancel()
}
}
}
```
此时可以访问房间里面的订阅者,对其进行验证。
- 当有订阅者订阅了某个流时,`OnSubscribeHooks.AddHook(onSubscribe)`
例如:
```go
func onSubscribe(s *OutputStream) {
if s.Publisher == nil {
go PullUpStream(s.StreamPath)
}
}
```
拉取源服务器的流

View File

@@ -1,4 +0,0 @@
# 更新历史
- 2020/1/27
初步完成

View File

@@ -1,104 +0,0 @@
# 内置插件介绍
内置插件为Monibuca提供了许多基础功能当然你完全可以不采用内置插件而改用自己开发的插件也丝毫不会影响您使用Monibuca。
## Jessica插件
> 该插件源码位于plugins/jessica下
该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放
Jessibua播放器已内置于源码中该播放器通过js解码H264/H265并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.Jessica]
ListenAddr = ":8080"
```
### Flv格式支持
Jessica以及Jessibuca也支持采用WebSocket中传输Flv格式的方式进行通讯目前有部分CDN厂商已经支持这种方式进行传输。
>私有协议以及Flv格式的判断是通过URL后缀是否带有.flv来进行判断
## Rtmp插件
> 该插件源码位于plugins/rtmp下
实现了基本的rtmp传输协议包括接收来自OBS、ffmpeg等软件的推流以及来在Flash Player播放器的拉流。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.RTMP]
ListenAddr = ":1935"
```
## RecordFlv插件
> 该插件源码位于plugins/record下
实现了录制Flv文件的功能并且支持再次使用录制好的Flv文件作为发布者进行发布。在Monibuca的web界面的控制台中提供了对房间进行录制的操作按钮以及列出所有已经录制的文件的界面。
### 配置
配置中的Path 表示要保存的Flv文件的根路径可以使用相对路径或者绝对路径
```toml
[Plugins.RecordFlv]
Path="./resource"
```
## Http-Flv插件
> 该插件位于plugins/HDL下
实现了http-flv格式的拉流功能方便对接CDN厂商
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.HDL]
ListenAddr = ":2020"
```
## Cluster插件
> 该插件源码位于plugins/cluster下
实现了基本的集群功能,里面包含一对发布者和订阅者,分别在主从服务器中启用,进行连接。
起基本原理就是在主服务器启动端口监听从服务器收到播放请求时如果从服务器没有对应的发布者则向主服务器发起请求主服务器收到来自从服务器的请求时将该请求作为一个订阅者。从服务器则把tcp连接作为发布者实现视频流的传递过程。
### 配置
主服务器的配置是ListenAddr用来监听从服务器的请求。
从服务器的配置是Master,表示主服务器的地址。
当然服务器可以既是主也是从,即充当中转站。
```toml
[Plugins.Cluster]
Master = "localhost:2019"
ListenAddr = ":2019"
```
## HLS插件
> 该插件源码位于plugins/HLS下
该插件的作用是请求M3u8文件进行解码最终将TS视频流转码成裸的视频流进行发布。
注意该插件目前并没有实现生成HLS的功能。
## 网关插件
> 该插件位于plugins/gateway下
该插件是为web控制台界面提供api用来采集服务器的信息。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.GateWay]
ListenAddr = ":80"
```
如果80端口有其他用途可以换成别的端口比如有nginx反向代理。
## 校验插件
> 该插件位于plugins/auth下
该插件提供了基本的验证功能,其原理是
订阅流提供一个签名签名只可以使用一次把签名进行AES CBC 解密如果得到的解密字符串的前面部分就是和Key相同则通过验证。
### 配置
Key代表用来加密的Key
```toml
[Plugins.Auth]
Key="www.monibuca.com"
```

19597
dashboard/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +0,0 @@
{
"name": "dashboard",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"docs:build": "vuepress build docs",
"docs:dev": "vuepress dev docs"
},
"dependencies": {
"@antv/g2plot": "^0.11.22",
"@antv/g6": "^3.2.9",
"core-js": "^3.4.4",
"view-design": "^4.0.0",
"vue": "^2.6.10",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-vuex": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"iview-loader": "^1.3.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"loadash": "^1.0.0",
"vue-cli-plugin-iview": "^2.0.0",
"vue-cli-plugin-vuepress": "^0.1.1",
"vue-template-compiler": "^2.6.10",
"vuepress": "^0.10.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"no-console": "off",
"vue/no-parsing-error": [
2,
{
"x-invalid-end-tag": false
}
]
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Monibuca</title>
<script src="jessibuca/ajax.js"></script>
<script src="jessibuca/renderer.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -1,535 +0,0 @@
// a simple ajax
!(function () {
var jsonType = 'application/json';
var htmlType = 'text/html';
var xmlTypeRE = /^(?:text|application)\/xml/i;
var blankRE = /^\s*$/; // \s
/*
* default setting
* */
var _settings = {
type: "GET",
beforeSend: noop,
success: noop,
error: noop,
complete: noop,
context: null,
xhr: function () {
return new window.XMLHttpRequest();
},
accepts: {
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
crossDomain: false,
timeout: 0,
username: null,
password: null,
processData: true,
promise: noop
};
function noop() {
}
var ajax = function (options) {
//
var settings = extend({}, options || {});
//
for (var key in _settings) {
if (settings[key] === undefined) {
settings[key] = _settings[key];
}
}
//
try {
var q = {};
var promise = new Promise(function (resolve, reject) {
q.resolve = resolve;
q.reject = reject;
});
promise.resolve = q.resolve;
promise.reject = q.reject;
settings.promise = promise;
}
catch (e) {
//
settings.promise = {
resolve: noop,
reject: noop
};
}
//
if (!settings.crossDomain) {
settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 !== window.location.href;
}
var dataType = settings.dataType;
// jsonp
if (dataType === 'jsonp') {
//
var hasPlaceholder = /=\?/.test(settings.url);
if (!hasPlaceholder) {
var jsonpCallback = (settings.jsonp || 'callback') + '=?';
settings.url = appendQuery(settings.url, jsonpCallback)
}
return JSONP(settings);
}
// url
if (!settings.url) {
settings.url = window.location.toString();
}
//
serializeData(settings);
var mime = settings.accepts[dataType]; // mime
var baseHeader = {}; // header
var protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol; // protocol
var xhr = _settings.xhr();
var abortTimeout;
// X-Requested-With header
// For cross-domain requests, seeing as conditions for a preflight are
// akin to a jigsaw puzzle, we simply never set it to be sure.
// (it can always be set on a per-request basis or even using ajaxSetup)
// For same-domain requests, won't change header if already provided.
if (!settings.crossDomain && !baseHeader['X-Requested-With']) {
baseHeader['X-Requested-With'] = 'XMLHttpRequest';
}
// mime
if (mime) {
//
baseHeader['Accept'] = mime;
if (mime.indexOf(',') > -1) {
mime = mime.split(',', 2)[0]
}
//
xhr.overrideMimeType && xhr.overrideMimeType(mime);
}
// contentType
if (settings.contentType || (settings.data && settings.type.toUpperCase() !== 'GET')) {
baseHeader['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded; charset=UTF-8');
}
// headers
settings.headers = extend(baseHeader, settings.headers || {});
// on ready state change
xhr.onreadystatechange = function () {
// readystate
if (xhr.readyState === 4) {
clearTimeout(abortTimeout);
var result;
var error = false;
//
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'));
result = xhr.responseText;
try {
// xml
if (dataType === 'xml') {
result = xhr.responseXML;
}
// json
else if (dataType === 'json') {
result = blankRE.test(result) ? null : JSON.parse(result);
}
}
catch (e) {
error = e;
}
if (error) {
ajaxError(error, 'parseerror', xhr, settings);
}
else {
ajaxSuccess(result, xhr, settings);
}
}
else {
ajaxError(null, 'error', xhr, settings);
}
}
};
// async
var async = 'async' in settings ? settings.async : true;
// open
xhr.open(settings.type, settings.url, async, settings.username, settings.password);
// xhrFields
if (settings.xhrFields) {
for (var name in settings.xhrFields) {
xhr[name] = settings.xhrFields[name];
}
}
// Override mime type if needed
if (settings.mimeType && xhr.overrideMimeType) {
xhr.overrideMimeType(settings.mimeType);
}
// set request header
for (var name in settings.headers) {
// Support: IE<9
// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
// request header to a null-value.
//
// To keep consistent with other XHR implementations, cast the value
// to string and ignore `undefined`.
if (settings.headers[name] !== undefined) {
xhr.setRequestHeader(name, settings.headers[name] + "");
}
}
// before send
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort();
return false;
}
// timeout
if (settings.timeout > 0) {
abortTimeout = window.setTimeout(function () {
xhr.onreadystatechange = noop;
xhr.abort();
ajaxError(null, 'timeout', xhr, settings);
}, settings.timeout);
}
// send
xhr.send(settings.data ? settings.data : null);
return settings.promise;
};
/*
* method get
* */
ajax.get = function (url, data, success, dataType) {
if (isFunction(data)) {
dataType = dataType || success;
success = data;
data = undefined;
}
return ajax({
url: url,
data: data,
success: success,
dataType: dataType
});
};
/*
* method post
*
* dataType:
* */
ajax.post = function (url, data, success, dataType) {
if (isFunction(data)) {
dataType = dataType || success;
success = data;
data = undefined;
}
return ajax({
type: 'POST',
url: url,
data: data,
success: success,
dataType: dataType
})
};
/*
* method getJSON
* */
ajax.getJSON = function (url, data, success) {
if (isFunction(data)) {
success = data;
data = undefined;
}
return ajax({
url: url,
data: data,
success: success,
dataType: 'json'
})
};
/*
* method ajaxSetup
* */
ajax.ajaxSetup = function (target, settings) {
return settings ? extend(extend(target, _settings), settings) : extend(_settings, target);
};
/*
* utils
*
* */
// triggers and extra global event ajaxBeforeSend that's like ajaxSend but cancelable
function ajaxBeforeSend(xhr, settings) {
var context = settings.context;
//
if (settings.beforeSend.call(context, xhr, settings) === false) {
return false;
}
}
// ajax success
function ajaxSuccess(data, xhr, settings) {
var context = settings.context;
var status = 'success';
settings.success.call(context, data, status, xhr);
settings.promise.resolve(data, status, xhr);
ajaxComplete(status, xhr, settings);
}
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
function ajaxComplete(status, xhr, settings) {
var context = settings.context;
settings.complete.call(context, xhr, status);
}
// type: "timeout", "error", "abort", "parsererror"
function ajaxError(error, type, xhr, settings) {
var context = settings.context;
settings.error.call(context, xhr, type, error);
settings.promise.reject(xhr, type, error);
ajaxComplete(type, xhr, settings);
}
// jsonp
/*
* tks: https://www.cnblogs.com/rubylouvre/archive/2011/02/13/1953087.html
* */
function JSONP(options) {
//
var callbackName = options.jsonpCallback || 'jsonp' + (new Date().getTime());
var script = window.document.createElement('script');
var abort = function () {
// 设置 window.xxx = noop
if (callbackName in window) {
window[callbackName] = noop;
}
};
var xhr = {abort: abort};
var abortTimeout;
var head = window.document.getElementsByTagName('head')[0] || window.document.documentElement;
// ie8+
script.onerror = function (error) {
_error(error);
};
function _error(error) {
window.clearTimeout(abortTimeout);
xhr.abort();
ajaxError(error.type, xhr, error.type, options);
_removeScript();
}
window[callbackName] = function (data) {
window.clearTimeout(abortTimeout);
ajaxSuccess(data, xhr, options);
_removeScript();
};
//
serializeData(options);
script.src = options.url.replace(/=\?/, '=' + callbackName);
//
script.src = appendQuery(script.src, '_=' + (new Date()).getTime());
//
script.async = true;
// script charset
if (options.scriptCharset) {
script.charset = options.scriptCharset;
}
//
head.insertBefore(script, head.firstChild);
//
if (options.timeout > 0) {
abortTimeout = window.setTimeout(function () {
xhr.abort();
ajaxError('timeout', xhr, 'timeout', options);
_removeScript();
}, options.timeout);
}
// remove script
function _removeScript() {
if (script.clearAttributes) {
script.clearAttributes();
} else {
script.onload = script.onreadystatechange = script.onerror = null;
}
if (script.parentNode) {
script.parentNode.removeChild(script);
}
//
script = null;
delete window[callbackName];
}
return options.promise;
}
// mime to data type
function mimeToDataType(mime) {
return mime && (mime === htmlType ? 'html' : mime === jsonType ? 'json' : xmlTypeRE.test(mime) && 'xml') || 'text'
}
// append query
function appendQuery(url, query) {
return (url + '&' + query).replace(/[&?]{1,2}/, '?');
}
// serialize data
function serializeData(options) {
// formData
if (isObject(options) && !isFormData(options.data) && options.processData) {
options.data = param(options.data);
}
if (options.data && (!options.type || options.type.toUpperCase() === 'GET')) {
options.url = appendQuery(options.url, options.data);
}
}
// serialize
function serialize(params, obj, traditional, scope) {
var _isArray = isArray(obj);
for (var key in obj) {
var value = obj[key];
if (scope) {
key = traditional ? scope : scope + '[' + (_isArray ? '' : key) + ']';
}
// handle data in serializeArray format
if (!scope && _isArray) {
params.add(value.name, value.value);
}
else if (traditional ? _isArray(value) : isObject(value)) {
serialize(params, value, traditional, key);
}
else {
params.add(key, value);
}
}
}
// param
function param(obj, traditional) {
var params = [];
//
params.add = function (k, v) {
this.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
};
serialize(params, obj, traditional);
return params.join('&').replace('%20', '+');
}
// extend
function extend(target) {
var slice = Array.prototype.slice;
var args = slice.call(arguments, 1);
//
for (var i = 0, length = args.length; i < length; i++) {
var source = args[i] || {};
for (var key in source) {
if (source.hasOwnProperty(key) && source[key] !== undefined) {
target[key] = source[key];
}
}
}
return target;
}
// is object
function isObject(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}
// is formData
function isFormData(obj) {
return obj instanceof FormData;
}
// is array
function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}
// is function
function isFunction(value) {
return typeof value === "function";
}
// browser
window.ajax = ajax;
})();

File diff suppressed because one or more lines are too long

View File

@@ -1,463 +0,0 @@
function Jessibuca(opt) {
this.canvasElement = opt.canvas;
this.contextOptions = opt.contextOptions;
this.videoBuffer = opt.videoBuffer || 1
if (!opt.forceNoGL) this.initContextGL();
if (this.contextGL) {
this.initProgram();
this.initBuffers();
this.initTextures();
};
this.decoderWorker = new Worker(opt.decoder || '264_mp3.js')
var _this = this
function draw(output) {
_this.drawNextOutputPicture(_this.width, _this.height, null, output)
postMessage({ cmd: "setBuffer", buffer: output }, '*', [output[0].buffer, output[1].buffer, output[2].buffer])
}
this.decoderWorker.onmessage = function (event) {
var msg = event.data
switch (msg.cmd) {
case "init":
console.log("decoder worker init")
postMessage({ cmd: "setVideoBuffer", time: _this.videoBuffer }, "*")
if (_this.onLoad) {
_this.onLoad()
delete _this.onLoad;
}
break
case "initSize":
_this.width = msg.w
_this.height = msg.h
if (_this.isWebGL()) {
// var buffer = new ArrayBuffer(msg.w * msg.h + (msg.w * msg.h >> 1))
// this.postMessage({ cmd: "setBuffer", buffer: buffer }, [buffer])
}
else {
_this.initRGB(msg.w, msg.h)
}
break
case "render":
if (_this.onPlay) {
_this.onPlay()
delete _this.onPlay;
}
// if (msg.compositionTime) {
// console.log(msg.compositionTime)
// setTimeout(draw, msg.compositionTime, msg.output)
// } else {
// draw(msg.output)
// }
draw(msg.output)
break
case "initAudio":
_this.initAudioPlay(msg.frameCount, msg.samplerate, msg.channels)
break
case "playAudio":
_this.playAudio(msg.buffer)
break
case "print":
console.log(msg.text);
break
case "printErr":
console.error(msg.text);
break
}
}
};
window.AudioContext = window.AudioContext || window.webkitAudioContext;
function _unlock() {
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
context.resume();
var source = context.createBufferSource();
source.buffer = context.createBuffer(1, 1, 22050);
source.connect(context.destination);
if (source.noteOn)
source.noteOn(0);
else
source.start(0);
}
// document.addEventListener("mousedown", _unlock, true);
// document.addEventListener("touchend", _unlock, true);
Jessibuca.prototype.audioEnabled = function (flag) {
if (flag) {
_unlock()
this.audioEnabled = function (flag) {
if (flag) {
this.audioContext.resume();
} else {
this.audioContext.suspend();
}
}
}
}
Jessibuca.prototype.playAudio = function (data) {
var context = this.audioContext;
var isPlaying = false;
var isDecoding = false;
if (!context) return false;
var audioBuffers = [];
var decodeQueue = []
var _this = this
var playNextBuffer = function (e) {
// isPlaying = false;
if (audioBuffers.length) {
playBuffer(audioBuffers.shift())
}
//if (audioBuffers.length > 1) audioBuffers.shift();
};
var playBuffer = function (buffer) {
isPlaying = true;
var audioBufferSouceNode = context.createBufferSource();
audioBufferSouceNode.buffer = buffer;
audioBufferSouceNode.connect(context.destination);
// audioBufferSouceNode.onended = playNextBuffer;
audioBufferSouceNode.start();
if (!_this.audioInterval) {
_this.audioInterval = setInterval(playNextBuffer, buffer.duration * 1000 - 1);
}
// setTimeout(playNextBuffer, buffer.duration * 1000)
}
var decodeAudio = function () {
if (decodeQueue.length) {
context.decodeAudioData(decodeQueue.shift(), tryPlay, decodeAudio);
} else {
isDecoding = false
}
}
var tryPlay = function (buffer) {
decodeAudio()
if (isPlaying) {
audioBuffers.push(buffer);
} else {
playBuffer(buffer)
}
}
var playAudio = function (data) {
decodeQueue.push(...data)
if (!isDecoding) {
isDecoding = true
decodeAudio()
}
}
this.playAudio = playAudio
playAudio(data)
}
Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels) {
var context = this.audioContext;
var isPlaying = false;
var audioBuffers = [];
if (!context) return false;
var resampled = samplerate < 22050;
var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate);
var playNextBuffer = function () {
isPlaying = false;
console.log("~", audioBuffers.length)
if (audioBuffers.length) {
playAudio(audioBuffers.shift());
}
//if (audioBuffers.length > 1) audioBuffers.shift();
};
var copyToCtxBuffer = channels > 1 ? function (fromBuffer) {
for (var channel = 0; channel < channels; channel++) {
var nowBuffering = audioBuffer.getChannelData(channel);
if (resampled) {
for (var i = 0; i < frameCount; i++) {
nowBuffering[i * 2] = nowBuffering[i * 2 + 1] = fromBuffer[i * (channel + 1)] / 32768;
}
} else
for (var i = 0; i < frameCount; i++) {
nowBuffering[i] = fromBuffer[i * (channel + 1)] / 32768;
}
}
} : function (fromBuffer) {
var nowBuffering = audioBuffer.getChannelData(0);
for (var i = 0; i < nowBuffering.length; i++) {
nowBuffering[i] = fromBuffer[i] / 32768;
}
// nowBuffering.set(fromBuffer);
};
var playAudio = function (fromBuffer) {
if (isPlaying) {
audioBuffers.push(fromBuffer);
console.log(audioBuffers.length)
return;
}
isPlaying = true;
copyToCtxBuffer(fromBuffer);
var source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.onended = playNextBuffer;
//setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
source.start();
};
this.playAudio = playAudio;
}
/**
* Returns true if the canvas supports WebGL
*/
Jessibuca.prototype.isWebGL = function () {
return !!this.contextGL;
};
/**
* Create the GL context from the canvas element
*/
Jessibuca.prototype.initContextGL = function () {
var canvas = this.canvasElement;
var gl = null;
var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
var nameIndex = 0;
while (!gl && nameIndex < validContextNames.length) {
var contextName = validContextNames[nameIndex];
try {
if (this.contextOptions) {
gl = canvas.getContext(contextName, this.contextOptions);
} else {
gl = canvas.getContext(contextName);
};
} catch (e) {
gl = null;
}
if (!gl || typeof gl.getParameter !== "function") {
gl = null;
}
++nameIndex;
};
this.contextGL = gl;
};
/**
* Initialize GL shader program
*/
Jessibuca.prototype.initProgram = function () {
var gl = this.contextGL;
var vertexShaderScript = [
'attribute vec4 vertexPos;',
'attribute vec4 texturePos;',
'varying vec2 textureCoord;',
'void main()',
'{',
'gl_Position = vertexPos;',
'textureCoord = texturePos.xy;',
'}'
].join('\n');
var fragmentShaderScript = [
'precision highp float;',
'varying highp vec2 textureCoord;',
'uniform sampler2D ySampler;',
'uniform sampler2D uSampler;',
'uniform sampler2D vSampler;',
'const mat4 YUV2RGB = mat4',
'(',
'1.1643828125, 0, 1.59602734375, -.87078515625,',
'1.1643828125, -.39176171875, -.81296875, .52959375,',
'1.1643828125, 2.017234375, 0, -1.081390625,',
'0, 0, 0, 1',
');',
'void main(void) {',
'highp float y = texture2D(ySampler, textureCoord).r;',
'highp float u = texture2D(uSampler, textureCoord).r;',
'highp float v = texture2D(vSampler, textureCoord).r;',
'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
'}'
].join('\n');
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderScript);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
}
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderScript);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
}
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
}
gl.useProgram(program);
this.shaderProgram = program;
};
/**
* Initialize vertex buffers and attach to shader program
*/
Jessibuca.prototype.initBuffers = function () {
var gl = this.contextGL;
var program = this.shaderProgram;
var vertexPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
gl.enableVertexAttribArray(vertexPosRef);
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
var texturePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
var texturePosRef = gl.getAttribLocation(program, 'texturePos');
gl.enableVertexAttribArray(texturePosRef);
gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
this.texturePosBuffer = texturePosBuffer;
};
/**
* Initialize GL textures and attach to shader program
*/
Jessibuca.prototype.initTextures = function () {
var gl = this.contextGL;
var program = this.shaderProgram;
var yTextureRef = this.initTexture();
var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
gl.uniform1i(ySamplerRef, 0);
this.yTextureRef = yTextureRef;
var uTextureRef = this.initTexture();
var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
gl.uniform1i(uSamplerRef, 1);
this.uTextureRef = uTextureRef;
var vTextureRef = this.initTexture();
var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
gl.uniform1i(vSamplerRef, 2);
this.vTextureRef = vTextureRef;
};
/**
* Create and configure a single texture
*/
Jessibuca.prototype.initTexture = function () {
var gl = this.contextGL;
var textureRef = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textureRef);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return textureRef;
};
/**
* Draw picture data to the canvas.
* If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
* Otherwise, data must be an RGBA formatted ArrayBuffer.
*/
Jessibuca.prototype.drawNextOutputPicture = function (width, height, croppingParams, data) {
var gl = this.contextGL;
if (gl) {
this.drawNextOuptutPictureGL(width, height, croppingParams, data);
} else {
this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
}
};
/**
* Draw the next output picture using WebGL
*/
Jessibuca.prototype.drawNextOuptutPictureGL = function (width, height, croppingParams, data) {
var gl = this.contextGL;
var texturePosBuffer = this.texturePosBuffer;
var yTextureRef = this.yTextureRef;
var uTextureRef = this.uTextureRef;
var vTextureRef = this.vTextureRef;
this.contextGL.viewport(0, 0, this.canvasElement.width, this.canvasElement.height);
// if (!croppingParams) {
// gl.viewport(0, 0, width, height);
// } else {
// gl.viewport(0, 0, croppingParams.width, croppingParams.height);
// var tTop = croppingParams.top / height;
// var tLeft = croppingParams.left / width;
// var tBottom = croppingParams.height / height;
// var tRight = croppingParams.width / width;
// var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
// gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
// gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
// }
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[0]);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[1]);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[2]);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
/**
* Draw next output picture using ARGB data on a 2d canvas.
*/
Jessibuca.prototype.drawNextOuptutPictureRGBA = function (width, height, croppingParams, data) {
// var canvas = this.canvasElement;
//var argbData = data;
//var ctx = canvas.getContext('2d');
// var imageData = ctx.getImageData(0, 0, width, height);
//this.imageData = this.ctx2d.getImageData(0, 0, width, height);
this.imageData.data.set(data);
//Module.print(typeof this.imageData.data);
if (!croppingParams) {
this.ctx2d.putImageData(this.imageData, 0, 0);
} else {
this.ctx2d.putImageData(this.imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
}
};
Jessibuca.prototype.ctx2d = null;
Jessibuca.prototype.imageData = null;
Jessibuca.prototype.initRGB = function (width, height) {
this.ctx2d = this.canvasElement.getContext('2d');
this.imageData = this.ctx2d.getImageData(0, 0, width, height);
this.clear = function () {
this.ctx2d.clearRect(0, 0, width, height)
};
//Module.print(this.imageData);
};
Jessibuca.prototype.close = function () {
if (this.audioInterval) {
clearInterval(this.audioInterval)
}
this.decoderWorker.postMessage({ cmd: "close" })
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
}
Jessibuca.prototype.destroy = function () {
this.decoderWorker.terminate()
}
Jessibuca.prototype.play = function (url) {
this.decoderWorker.postMessage({ cmd: "play", url: url, isWebGL: this.isWebGL() })
}

View File

@@ -1,71 +0,0 @@
<template>
<div id="app">
<div>Monibuca</div>
<Menu mode="horizontal" :active-name="selectedMenu" style="position: absolute;top: 0;right: 0;">
<MenuItem name="home" to="/">首页</MenuItem>
<MenuItem name="docs" to="/docs" target="_blank">文档</MenuItem>
<MenuItem name="console" to="console">控制台</MenuItem>
<Submenu name="plugins">
<template slot="title">内置插件</template>
<MenuGroup title="发布者/订阅者">
<MenuItem name="cluster" target="_blank" to="/docs/plugins.html#cluster插件">集群</MenuItem>
<MenuItem name="rtmp" target="_blank" to="/docs/plugins.html#rtmp插件">RTMP</MenuItem>
</MenuGroup>
<MenuGroup title="订阅者">
<MenuItem name="jessica" target="_blank" to="/docs/plugins.html#jessica插件">Jessica</MenuItem>
<MenuItem name="HDL" target="_blank" to="/docs/plugins.html#http-flv插件">Http-Flv</MenuItem>
<MenuItem name="record" target="_blank" to="/docs/plugins.html#recordflv插件">录制Flv</MenuItem>
</MenuGroup>
<MenuGroup title="发布者">
<MenuItem name="HLS" target="_blank" to="/docs/plugins.html#hls插件">HLS</MenuItem>
<MenuItem name="TS" target="_blank" to="/docs/plugins.html#hls插件">TS</MenuItem>
</MenuGroup>
<MenuGroup title="钩子">
<MenuItem name="Auth" target="_blank" to="/docs/plugins.html#校验插件">验证</MenuItem>
<MenuItem name="QoS">QoS</MenuItem>
<MenuItem name="gateway" target="_blank" to="/docs/plugins.html#网关插件">网关</MenuItem>
</MenuGroup>
</Submenu>
<MenuItem name="4" to="about">支持</MenuItem>
</Menu>
<router-view class="content"></router-view>
<div>Copyright © 2019-2020 dexter 苏ICP备20001212号</div>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
selectedMenu: "home"
};
}
};
</script>
<style>
body,
html {
height: 100%;
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #184c18;
position: relative;
height: 100%;
}
#app > div:first-child {
position: absolute;
top: 10px;
left: 30px;
font-size: x-large;
}
.content {
padding-top: 60px;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1,97 +0,0 @@
<template>
<div id="mountNode"></div>
</template>
<script>
import { mapState } from "vuex";
import G6 from "@antv/g6";
var graph = null;
export default {
computed: {
...mapState({
data(state) {
let summary = state.summary;
// 点集
let nodes = [];
// 边集
let edges = [];
this.addServer(summary, nodes, edges);
return {
nodes,
edges
};
}
})
},
methods: {
addServer(node, nodes, edges) {
let result = {
id: node.Address,
label: node.Address,
description: `cpu:${node.CPUUsage >> 0}% mem:${node.Memory.Usage >>
0}%`,
shape: "modelRect",
logoIcon: {
show: false
}
};
nodes.push(result);
if (node.Rooms) {
for (let i = 0; i < node.Rooms.length; i++) {
let room = node.Rooms[i];
let roomId = result.id + room.StreamPath;
nodes.push({
id: roomId,
label: room.StreamPath,
shape: "rect"
});
edges.push({ source: result.id, target: roomId });
if (room.SubscriberInfo) {
for (let j = 0; j < room.SubscriberInfo.length; j++) {
let subId = roomId + room.SubscriberInfo[j].ID;
nodes.push({
id: subId,
label: room.SubscriberInfo[j].ID
});
edges.push({ source: roomId, target: subId });
}
}
}
}
if (node.Children && node.Children.length > 0) {
for (let i = 0; i < node.Children.length; i++) {
let child = this.addServer(node.Children[i], nodes, edges);
edges.push({
source: result.id,
target: child.id
});
}
}
return result;
}
},
watch: {
data(v) {
if (graph) {
graph.read(v); // 加载数据
}
}
},
mounted() {
graph = new G6.Graph({
renderer: "svg",
container: "mountNode", // 指定挂载容器
width: 800, // 图的宽度
height: 500, // 图的高度
layout: {
type: "radial"
},
defaultNode: {}
});
graph.read(this.data); // 加载数据
}
};
</script>
<style>
</style>

View File

@@ -1,29 +0,0 @@
<template>
<div style="padding:0 15px">
<pre>{{config}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
config: ""
};
},
methods: {
onVisible(visible) {
if (visible) {
window.ajax.get(
"//" + location.host + "/api/config",
{},
x => (this.config = x)
);
}
}
}
};
</script>
<style>
</style>

View File

@@ -1,60 +0,0 @@
<template>
<Modal
v-bind="$attrs"
draggable
v-on="$listeners"
:title="url"
@on-ok="onClosePreview"
@on-cancel="onClosePreview"
>
<canvas id="canvas" width="488" height="275" style="background: black" />
<div slot="footer">
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-off" />
<Button v-else @click="turnOn" icon="md-volume-up"></Button>
</div>
</Modal>
</template>
<script>
let h5lc = null;
export default {
name: "Jessibuca",
data() {
return {
audioEnabled: false,
url: ""
};
},
watch: {
audioEnabled(value) {
h5lc.audioEnabled(value);
}
},
mounted() {
h5lc = new window.Jessibuca({
canvas: document.getElementById("canvas"),
decoder: "jessibuca/ff.js"
});
},
destroyed() {
this.onClosePreview();
h5lc.destroy();
},
methods: {
play(url) {
this.url = url;
h5lc.play(url);
},
onClosePreview() {
h5lc.close();
},
turnOn() {
this.audioEnabled = true;
},
turnOff() {
this.audioEnabled = false;
}
}
};
</script>

View File

@@ -1,43 +0,0 @@
<template>
<div style="padding:0 15px">
<div>
自动滚动
<Switch v-model="autoScroll" />
</div>
<div ref="logContainer" class="log-container">
<pre><template v-for="item in $store.state.logs">{{item+"\n"}}</template></pre>
</div>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
data() {
return {
autoScroll: true
};
},
mounted() {
this.fetchLogs();
},
destroyed() {
this.stopFetchLogs();
},
methods: {
...mapActions(["fetchLogs", "stopFetchLogs"])
},
updated() {
if (this.autoScroll) {
this.$refs.logContainer.scrollTop = this.$refs.logContainer.offsetHeight;
}
}
};
</script>
<style>
.log-container {
overflow-y: auto;
max-height: 500px;
}
</style>

View File

@@ -1,112 +0,0 @@
<template>
<div class="records">
<Card v-for="item in data" :key="item">
<p slot="title">{{item.Path}}</p>
<div slot="extra">
<Button @click="play(item)" icon="md-play" size="small"></Button>
<Button @click="deleteFlv(item)" icon="ios-trash" size="small"></Button>
</div>
{{toSizeStr(item.Size)}} {{toDurationStr(item.Duration)}}
</Card>
</div>
</template>
<script>
const uintInc = {
"": "K",
K: "M",
M: "G",
G: null
};
export default {
data() {
return {
data: []
};
},
methods: {
play(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv/play",
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.onVisible(true);
this.$Message.success("删除成功");
} else {
this.$Message.error(x);
}
}
);
},
deleteFlv(item) {
this.$Modal.confirm({
title: "提示",
content: "<p>是否删除Flv文件</p>",
onOk: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv/delete",
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.$Message.success("开始发布");
} else {
this.$Message.error(x);
}
}
);
},
onCancel: () => {}
});
},
toSizeStr(value, unit = "") {
if (value > 1024 && uintInc[unit]) {
return this.toSizeStr(value / 1024, uintInc[unit]);
}
return value.toFixed(2).replace(".00", "") + unit + "B";
},
toDurationStr(value) {
if (value > 1000) {
let s = value / 1000;
if (s > 60) {
s = s | 0;
let min = (s / 60) >> 0;
if (min > 60) {
let hour = (min / 60) >> 0;
return hour + "hour" + (min % 60) + "min";
} else {
return min + "min" + (s % 60) + "s";
}
} else {
return s.toFixed(3) + "s";
}
} else {
return value + "ms";
}
},
onVisible(visible) {
if (visible) {
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
}
);
}
}
}
};
</script>
<style scoped>
.records {
display: flex;
flex-wrap: wrap;
padding: 0 15px;
}
.records > * {
width: 200px;
}
</style>

View File

@@ -1,18 +0,0 @@
<template>
<Poptip trigger="hover" :content="'⌚️'+ new Date(value).toLocaleString()">
<Time :time="new Date(value)"></Time>
</Poptip>
</template>
<script>
export default {
name: "StartTime",
props:{
value:String
}
}
</script>
<style scoped>
</style>

View File

@@ -1,62 +0,0 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" title="查看订阅者">
<Table :columns="subtableColumns" :data="data"></Table>
</Modal>
</template>
<script>
export default {
props: {
data: Array
},
data() {
return {
subtableColumns: [
{
title: "类型",
key: "Type"
},
{
title: "Name",
key: "ID"
},
{
title: "订阅时间",
render(h, { row }) {
return h("StartTime", {
props: {
value: row.SubscribeTime
}
});
}
},
{
title: "丢帧",
render(h, { row }) {
return h(
"span",
row.TotalPacket ? row.TotalDrop + "/" + row.TotalPacket : ""
);
}
},
{
title: "Buffer",
render(h, { row }) {
return h("Progress", {
props: {
percent: Math.floor((row.BufferLength * 99) / 1024),
"text-inside": true,
"stroke-width": 20,
"stroke-color": ["#87d068", "#ff0000"]
}
});
}
}
]
};
}
};
</script>
<style>
</style>

View File

@@ -1,13 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/iview.js'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

View File

@@ -1,6 +0,0 @@
import Vue from 'vue'
import ViewUI from 'view-design'
Vue.use(ViewUI)
import 'view-design/dist/styles/iview.css'

View File

@@ -1,32 +0,0 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About'
import Console from '../views/Console'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}, {
path: '/console',
name: 'console',
component: Console
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

View File

@@ -1,63 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let summaryES = null
let logsES = null
export default new Vuex.Store({
state: {
summary: {
Address: location.hostname,
NetWork: [],
Rooms: [],
Memory: {
Used: 0,
Usage: 0
},
CPUUsage: 0,
HardDisk: {
Used: 0,
Usage: 0
},
Children: {}
}, logs: []
},
mutations: {
update(state, payload) {
Object.assign(state, payload)
},
addLog(state, payload) {
state.logs.push(payload)
}
},
actions: {
fetchSummary({ commit }) {
summaryES = new EventSource(
"//" + location.host + "/api/summary"
);
summaryES.onmessage = evt => {
if (!evt.data) return
let summary = JSON.parse(evt.data)
summary.Address = location.hostname
commit("update", { summary })
}
},
fetchLogs({ commit }) {
logsES = new EventSource(
"//" + location.host + "/api/logs"
)
logsES.onmessage = evt => {
if (!evt.data) return
commit("addLog", evt.data)
}
},
stopFetchLogs() {
logsES.close()
},
stopFetchSummary() {
summaryES.close()
}
},
modules: {
}
})

View File

@@ -1,21 +0,0 @@
<template>
<div class="root">
<h1>
赞助 Monibuca 的研发
</h1>
<p>
Monibuca 是采用 MIT 许可的开源项目使用完全免费 但是随着项目规模的增长也需要有相应的资金支持才能持续项目的维护的开发你可以通过下列的方法来赞助 Monibuca 的开发
</p>
<img src="../assets/alipay.png">
<img src="../assets/wechat.jpg">
</div>
</template>
<style scoped>
.root{
background: lightgray;
}
.root>img{
width: 300px;
margin: 30px;
}
</style>

View File

@@ -1,325 +0,0 @@
<template>
<div style="text-align:left;">
<Tabs v-model="currentTab" @on-click="onChangeTab">
<TabPane label="直播流" icon="md-videocam">
<div class="layout">
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
<StartTime slot="extra" :value="item.StartTime"></StartTime>
<p>
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
</p>
<p>
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
</p>
<ButtonGroup size="small">
<Button @click="onShowDetail(item)" icon="ios-people">{{getSubscriberCount(item)}}</Button>
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
<Button
@click="stopRecord(item)"
class="recording"
v-if="isRecording(item)"
icon="ios-radio-button-on"
></Button>
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
</div>
</div>
</TabPane>
<TabPane label="集群总览" icon="ios-cloud">
<Cluster />
</TabPane>
<TabPane label="录制的视频" icon="ios-folder" name="recordsPanel">
<Records ref="recordsPanel" />
</TabPane>
<TabPane label="日志跟踪" icon="md-bug">
<Logs />
</TabPane>
<TabPane label="查看配置" icon="md-settings" name="configPanel">
<Config ref="configPanel" />
</TabPane>
</Tabs>
<div class="status">
<Alert>带宽消耗 📥{{totalInNetSpeed}} 📤{{totalOutNetSpeed}}</Alert>
<Alert
:type="memoryStatus"
>内存使用{{networkFormat(Memory.Used,"M")}} 占比{{Memory.Usage.toFixed(2)}}%</Alert>
<Alert :type="cpuStatus">CPU使用{{CPUUsage.toFixed(2)}}%</Alert>
<Alert
:type="hardDiskStatus"
>磁盘使用{{networkFormat(HardDisk.Used,"M")}} 占比{{HardDisk.Usage.toFixed(2)}}%</Alert>
</div>
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
<Subscribers :data="currentStream && currentStream.SubscriberInfo" v-model="showSubscribers" />
</div>
</template>
<script>
import { mapActions, mapState } from "vuex";
import Jessibuca from "../components/Jessibuca";
import StartTime from "../components/StartTime";
import Records from "../components/Records";
import Logs from "../components/Logs";
import Config from "../components/Config";
import Subscribers from "../components/Subscribers";
import Cluster from "../components/Cluster";
const uintInc = {
"": "K",
K: "M",
M: "G",
G: null
};
const SoundFormat = {
0: "Linear PCM, platform endian",
1: "ADPCM",
2: "MP3",
3: "Linear PCM, little endian",
4: "Nellymoser 16kHz mono",
5: "Nellymoser 8kHz mono",
6: "Nellymoser",
7: "G.711 A-law logarithmic PCM",
8: "G.711 mu-law logarithmic PCM",
9: "reserved",
10: "AAC",
11: "Speex",
14: "MP3 8Khz",
15: "Device-specific sound"
};
const CodecID = {
1: "JPEG (currently unused)",
2: "Sorenson H.263",
3: "Screen video",
4: "On2 VP6",
5: "On2 VP6 with alpha channel",
6: "Screen video version 2",
7: "AVC",
12: "H265"
};
export default {
name: "Console",
components: {
Jessibuca,
StartTime,
Records,
Logs,
Subscribers,
Config,
Cluster
},
data() {
return {
showPreview: false,
showSubscribers: false,
currentTab: "",
currentStream: [],
typeMap: {
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
};
},
computed: {
...mapState({
Rooms: state => state.summary.Rooms || [],
Memory: state => state.summary.Memory,
CPUUsage: state => state.summary.CPUUsage,
HardDisk: state => state.summary.HardDisk,
cpuStatus: state => {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
memoryStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
hardDiskStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
totalInNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork
? state.summary.NetWork.reduce(
(aac, c) => aac + c.ReceiveSpeed,
0
)
: 0
) + "/S"
);
},
totalOutNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork
? state.summary.NetWork.reduce((aac, c) => aac + c.SentSpeed, 0)
: 0
) + "/S"
);
}
})
},
methods: {
...mapActions(["fetchSummary", "stopFetchSummary"]),
getSubscriberCount(item) {
if (
this.currentStream &&
this.currentStream.StreamPath == item.StreamPath
) {
this.currentStream = item;
}
return item.SubscriberInfo ? item.SubscriberInfo.length : 0;
},
preview(item) {
this.$refs.jessibuca.play(
"ws://" + location.hostname + ":8080/" + item.StreamPath
);
this.showPreview = true;
},
onShowDetail(item) {
this.showSubscribers = true;
this.currentStream = item;
},
networkFormat(value, unit = "") {
if (value > 1024 && uintInc[unit]) {
return this.networkFormat(value / 1024, uintInc[unit]);
}
return value.toFixed(2).replace(".00", "") + unit + "B";
},
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
this.$Modal.confirm({
title: "提示",
content: "<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
}
}
);
},
onCancel: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
} else {
this.$Message.error(x);
}
}
);
}
});
},
stopRecord(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
}
}
);
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
onChangeTab(name) {
switch (name) {
case "recordsPanel":
this.$refs.recordsPanel.onVisible(true);
break;
case "configPanel":
this.$refs.configPanel.onVisible(true);
}
}
},
mounted() {
this.fetchSummary();
},
destroyed() {
this.stopFetchSummary();
}
};
</script>
<style scoped>
@keyframes recording {
0% {
opacity: 0.2;
}
50% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
.recording {
animation: recording 1s infinite;
}
.layout {
padding-bottom: 30px;
display: flex;
flex-wrap: wrap;
}
.room {
width: 250px;
margin: 10px;
text-align: left;
}
.empty {
color: #eb5e46;
width: 100%;
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
.status {
position: fixed;
display: flex;
left: 5px;
bottom: 10px;
}
.status > div {
margin: 0 5px;
}
</style>

View File

@@ -1,53 +0,0 @@
<template>
<div>
<img src="../assets/logo.png">
<div>
<p>
Monibuca 是一个开源的Go语言实现的流媒体服务器开发框架
</p>
<Button type="success" to="/docs" target="_blank">🚀START</Button>
<span style="margin: 0 10px"></span>
<Button type="default" target="_blank" to="https://github.com/langhuihui/monibuca">
<svg style="vertical-align: text-top" width="16" height="16" aria-labelledby="simpleicons-github-dark-icon" lang="" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title id="simpleicons-github-dark-icon" lang="en">GitHub Dark icon</title><path fill="#7F8C8D" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg>
GITHUB</Button>
</div>
<Row style="margin: 30px;">
<Col span="8">
<Card :bordered="false" style="margin: 30px">
<div slot="title" class="feature-title">高性能</div>
<div>针对流媒体服务器独特的性质进行的优化充分利用Golang的goroutine的性质对大量的连接的读写进行合理的分配计算资源以及尽可能的减少内存Copy操作使用对象池减少Golang的GC时间</div>
</Card>
</Col>
<Col span="8">
<Card :bordered="false" style="margin: 30px">
<div slot="title" class="feature-title">🔧可扩展</div>
<div>流媒体服务器的个性化定制变的更简单基于Golang语言开发效率更高独创的插件机制可以方便用户定制个性化的功能组合更高效率的利用服务器资源</div>
</Card>
</Col>
<Col span="8">
<Card :bordered="false" style="margin: 30px">
<div slot="title" class="feature-title">📈可视化</div>
<div>功能强大的仪表盘可以直观的看到服务器运行的状态消耗的资源以及其他统计信息用户可以利用控制台对服务器进行配置和控制点击右上角菜单栏里面的控制台可以看到演示</div>
</Card>
</Col>
</Row>
</div>
</template>
<script>
</script>
<style scoped>
.feature-title{
color: #eb5e46;
font-weight: bold;
font-size: larger;
}
p {
margin: 30px;
font-size: 20px;
}
img{
margin: 20px;
}
</style>

View File

@@ -1,10 +0,0 @@
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('iview-loader')
.loader('iview-loader')
.tap(()=> ({prefix: false}))
.end()
}
}

45
gen.go Normal file
View File

@@ -0,0 +1,45 @@
//go:build ignore
package main
import (
"fmt"
"io/ioutil"
"os"
)
var debugShim string = `package main
import "net/http"
func init() {
notSupport := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("not support"))
}
http.HandleFunc("/debug/charts/", notSupport)
http.HandleFunc("/debug/charts/data", notSupport)
http.HandleFunc("/debug/charts/data-feed", notSupport)
}
`
var debug string = `package main
import (
_ "github.com/mkevac/debugcharts"
)
`
func main() {
if len(os.Args) < 2 {
fmt.Println("usage: go run gen.go <path>")
os.Exit(1)
}
var content string
if os.Args[1] == "1" {
content = debug
} else {
content = debugShim
}
ioutil.WriteFile("debug.go", []byte(content), 0666)
}

107
go.mod
View File

@@ -1,17 +1,98 @@
module github.com/langhuihui/monibuca
module monibuca
go 1.13
go 1.19
require (
github.com/BurntSushi/toml v0.3.1
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478
github.com/funny/utest v0.0.0-20161029064919-43870a374500 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
github.com/gobwas/pool v0.2.0 // indirect
github.com/gobwas/ws v1.0.2
github.com/quangngotan95/go-m3u8 v0.1.0
github.com/shirou/gopsutil v2.19.12+incompatible
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
m7s.live/engine/v4 v4.12.3
m7s.live/plugin/debug/v4 v4.0.0-20220506113031-34f3a736ceb2
m7s.live/plugin/edge/v4 v4.0.4
m7s.live/plugin/fmp4/v4 v4.0.2
m7s.live/plugin/gb28181/v4 v4.2.12
m7s.live/plugin/hdl/v4 v4.1.4
m7s.live/plugin/hls/v4 v4.2.1
m7s.live/plugin/hook/v4 v4.0.2
m7s.live/plugin/jessica/v4 v4.1.1
m7s.live/plugin/logrotate/v4 v4.0.3
m7s.live/plugin/monitor/v4 v4.0.0
m7s.live/plugin/preview/v4 v4.0.4
m7s.live/plugin/record/v4 v4.3.0
m7s.live/plugin/room/v4 v4.0.7
m7s.live/plugin/rtmp/v4 v4.1.8
m7s.live/plugin/rtsp/v4 v4.1.6
m7s.live/plugin/snap/v4 v4.0.4
m7s.live/plugin/webrtc/v4 v4.1.3
m7s.live/plugin/webtransport/v4 v4.0.2
)
require (
github.com/aler9/gortsplib/v2 v2.2.2 // indirect
github.com/cnotch/ipchub v1.1.0 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca // indirect
github.com/edgeware/mp4ff v0.30.1 // indirect
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mcuadros/go-defaults v1.2.0 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pion/datachannel v1.5.5 // indirect
github.com/pion/dtls/v2 v2.2.6 // indirect
github.com/pion/ice/v2 v2.3.1 // indirect
github.com/pion/interceptor v0.1.12 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.10 // indirect
github.com/pion/rtp v1.7.13 // indirect
github.com/pion/sctp v1.8.6 // indirect
github.com/pion/sdp/v3 v3.0.6 // indirect
github.com/pion/srtp/v2 v2.0.12 // indirect
github.com/pion/stun v0.4.0 // indirect
github.com/pion/transport/v2 v2.0.2 // indirect
github.com/pion/turn/v2 v2.1.0 // indirect
github.com/pion/udp/v2 v2.0.1 // indirect
github.com/pion/webrtc/v3 v3.1.56 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/q191201771/naza v0.30.8 // indirect
github.com/quangngotan95/go-m3u8 v0.1.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
github.com/quic-go/quic-go v0.32.0 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/shirou/gopsutil/v3 v3.22.10 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/yapingcat/gomedia v0.0.0-20230222121919-c67df405bf33 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

408
go.sum
View File

@@ -1,22 +1,392 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/aler9/gortsplib/v2 v2.2.2 h1:tTw8pdKSOEjlZjjE1S4ftXPHJkYOqjNNv3hjQ0Nto9M=
github.com/aler9/gortsplib/v2 v2.2.2/go.mod h1:k6uBVHGwsIc/0L5SLLqWwi6bSJUb4VR0HfvncyHlKQI=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cnotch/apirouter v0.0.0-20200731232942-89e243a791f3/go.mod h1:5deJPLON/x/s2dLOQfuKS0lenhOIT4xX0pvtN/OEIuY=
github.com/cnotch/ipchub v1.1.0 h1:hH0lh2mU3AZXPiqMwA0pdtqrwo7PFIMRGush9OobMUs=
github.com/cnotch/ipchub v1.1.0/go.mod h1:2PbeBs2q2VxxTVCn1eYCDwpAWuVXbq1+N0FU7GimOH4=
github.com/cnotch/loader v0.0.0-20200405015128-d9d964d09439/go.mod h1:oWpDagHB6p+Kqqq7RoRZKyC4XAXft50hR8pbTxdbYYs=
github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/queue v0.0.0-20201224060551-4191569ce8f6/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/scheduler v0.0.0-20200522024700-1d2da93eefc5/go.mod h1:F4GE3SZkJZ8an1Y0ZCqvSM3jeozNuKzoC67erG1PhIo=
github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl3sR3yMGgxYupMn2btzdtJUwoxFPUE5E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca h1:cTTdXpkQ1aVbOOmHwdwtYuwUZcQtcMrleD1UXLWhAq8=
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0=
github.com/edgeware/mp4ff v0.30.1 h1:OV88fQrw8sFPjTCFThsBwubjYPHTdHgb28pSd1y9ozk=
github.com/edgeware/mp4ff v0.30.1/go.mod h1:GNUeA6tEFksH2CrjJF2FSGdJolba8yPGmo16qZTXsm8=
github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d h1:f1JRfm0MwkluwtUsbYxuVReDMajlc9Wn6zc2orX4sRE=
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d/go.mod h1:yTr3BEYSFe9As6XM7ldyrVgqsPwlnw8Ahc4N28VFM2g=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0-rc.1/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8 h1:4Jk58quTZmzJcTrLlbB5L1Q6qXu49EIjCReWxcBFWKo=
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8/go.mod h1:medl9/CfYoQlqAXtAARmMW5dAX2UOdwwkhaszYPk0AM=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE=
github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es=
github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.5/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
github.com/pion/ice/v2 v2.3.1 h1:FQCmUfZe2Jpe7LYStVBOP6z1DiSzbIateih3TztgTjc=
github.com/pion/ice/v2 v2.3.1/go.mod h1:aq2kc6MtYNcn4XmMhobAv6hTNJiHzvD0yXRz80+bnP8=
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.6 h1:CUex11Vkt9YS++VhLf8b55O3VqKrWL6W3SDwX4jAqsI=
github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v2 v2.0.12 h1:WrmiVCubGMOAObBU1vwWjG0H3VSyQHawKeer2PVA5rY=
github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y=
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk=
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg=
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0=
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
github.com/pion/webrtc/v3 v3.1.56 h1:ScaiqKQN3liQwT+kJwOBaYP6TwSfixzdUnZmzHAo0a0=
github.com/pion/webrtc/v3 v3.1.56/go.mod h1:7VhbA6ihqJlz6R/INHjyh1b8HpiV9Ct4UQvE1OB/xoM=
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/q191201771/naza v0.30.8 h1:Lhh29o65C4PmTDj2l+eKfsw9dddpgWZk4bFICtcnSaA=
github.com/q191201771/naza v0.30.8/go.mod h1:n+dpJjQSh90PxBwxBNuifOwQttywvSIN5TkWSSYCeBk=
github.com/quangngotan95/go-m3u8 v0.1.0 h1:8oseBjJn5IKHQKdRZwSNskkua3NLrRtlvXXtoVgBzMk=
github.com/quangngotan95/go-m3u8 v0.1.0/go.mod h1:smzfWHlYpBATVNu1GapKLYiCtEo5JxridIgvvudZ+Wc=
github.com/shirou/gopsutil v2.19.12+incompatible h1:WRstheAymn1WOPesh+24+bZKFkqrdCR8JOc77v4xV3Q=
github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=
github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/yapingcat/gomedia v0.0.0-20230222121919-c67df405bf33 h1:uyZY++dluUg7iTSsNzuOVln/mC2U2KXwgKLfKLCJ74Y=
github.com/yapingcat/gomedia v0.0.0-20230222121919-c67df405bf33/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
m7s.live/engine/v4 v4.12.3 h1:QHr3gvAepeJMd6XHYVJegqHKrITg+qiZ7bOcjAY+vsE=
m7s.live/engine/v4 v4.12.3/go.mod h1:AiJPBwdA77DM3fymlcH2qYPR8ivL6ib9UVLm1Rft/to=
m7s.live/plugin/debug/v4 v4.0.0-20220506113031-34f3a736ceb2 h1:0QWJvozTHy7m+G/lOIGiejQ9okFIDjwzVsgXe6ZaJg8=
m7s.live/plugin/debug/v4 v4.0.0-20220506113031-34f3a736ceb2/go.mod h1:A/C1ViacCOK7EPeeCfiwlUrC2TUtRdEqzrR0Yd9b1KQ=
m7s.live/plugin/edge/v4 v4.0.4 h1:NvvYeAbpA1/Vg8dx5e4as/o+PbSCiSI2xoRkKOXBjhI=
m7s.live/plugin/edge/v4 v4.0.4/go.mod h1:AWeJV1Nu2UWsEdlcCATlQW+bz6Pz/3A11AZG9Z4hIc0=
m7s.live/plugin/fmp4/v4 v4.0.2 h1:IHAFEEktDzHoEhsfonCzMt9lNttp1Qz2R2u4wfOCDos=
m7s.live/plugin/fmp4/v4 v4.0.2/go.mod h1:9/DlkfpXuBtZziwARFHZogLRnT+vjXPbO5eTrbGkY+8=
m7s.live/plugin/gb28181/v4 v4.2.12 h1:p0ekloW+TyoClObDm8hF/HKxK+DEPipH0x2Ruv3p/aY=
m7s.live/plugin/gb28181/v4 v4.2.12/go.mod h1:zz6vOdI97lwLVPIJEeYQlASrKfWn3QPt+g4yt7gVx5c=
m7s.live/plugin/hdl/v4 v4.1.4 h1:iIvf8RBqd3EIQdol4aMarLHYpe1ukOvzfVhB9/yJpnY=
m7s.live/plugin/hdl/v4 v4.1.4/go.mod h1:tUcbO5AZLj4Kg5UILxhYrDdSHnNNWPMQxN/+NFQuaTE=
m7s.live/plugin/hls/v4 v4.2.1 h1:okwGNHuw/yco+TAKrhantzj9cmoPzD+FTRB5334pr+M=
m7s.live/plugin/hls/v4 v4.2.1/go.mod h1:PH2AORCqWeBUveHFXxkCYLXoFCwgAVoE7hCqgj4lPR8=
m7s.live/plugin/hook/v4 v4.0.2 h1:fKwsx7gDAzfKfCz0C+knoY4wg/qnlQloM996l3jUWNc=
m7s.live/plugin/hook/v4 v4.0.2/go.mod h1:zoyGSwIIdt4NGFdqrKHLFCwwMk4oAOF+dlvSV6cizGU=
m7s.live/plugin/jessica/v4 v4.1.1 h1:+0rW+z5vZC2M2MpFRlk/VW8ugvfdil1s5UPMdeHoigo=
m7s.live/plugin/jessica/v4 v4.1.1/go.mod h1:PiKCNriToVPytx5WMAtE26i8k1g7eP7Pud1msrU3X6g=
m7s.live/plugin/logrotate/v4 v4.0.3 h1:f+teASc5Y5vDOrxXFc/ri3t7jdi6GIb6V9UMjhacb7I=
m7s.live/plugin/logrotate/v4 v4.0.3/go.mod h1:a8hIV59GjpQDE+Ip790CU7yOHaG4+Iweci0sLvo43FU=
m7s.live/plugin/monitor/v4 v4.0.0 h1:YfMbaJ5jVWkqa0wFRsh04c8A7O0+VI29KdVBxxTR+q0=
m7s.live/plugin/monitor/v4 v4.0.0/go.mod h1:wYcjvXM03RK00DGA/R7zfiENNT7jDFQhp0rEBvO1mTw=
m7s.live/plugin/preview/v4 v4.0.4 h1:xDS0817CLo3hx+wAcncE46gPDiPN5AsIWdAdbBltGDA=
m7s.live/plugin/preview/v4 v4.0.4/go.mod h1:iYhWqFbTvTNDFplbTOrE5X9M6ED7gCKkKMuM5QmBocg=
m7s.live/plugin/record/v4 v4.3.0 h1:yfR221UazsRU43YOPUgd6vmwMIbMm6q4mleHa0MjdcU=
m7s.live/plugin/record/v4 v4.3.0/go.mod h1:SlOV0HCGNPn+AI1EjgttnA3KgQ2xccEUomBeVv4KKKM=
m7s.live/plugin/room/v4 v4.0.7 h1:K+KKLa+tr3eDCsQCgmpF8/QXYkro6zhmAFCOgkvLUro=
m7s.live/plugin/room/v4 v4.0.7/go.mod h1:etzDx/MHFm1XPDNwqprJbtY8iXLYiG3KeMg62z7MiSw=
m7s.live/plugin/rtmp/v4 v4.1.8 h1:r2bojw5+ZrwGEpb2quB8N3mbjqZtuCIN//vfapq9yxA=
m7s.live/plugin/rtmp/v4 v4.1.8/go.mod h1:7+3oO93TIsSlASnv482PCE3CXhYqyRIAwA8FllnOTgE=
m7s.live/plugin/rtsp/v4 v4.1.6 h1:Tx81OZKRPqzLmnubr48tCPMUHyzpKMkvyvfYYm4H/XI=
m7s.live/plugin/rtsp/v4 v4.1.6/go.mod h1:+fIAAY/NLV0PgfvddPCgcm07EusqTC+nVUF/KhYYtCo=
m7s.live/plugin/snap/v4 v4.0.4 h1:v9QGkIFA3Q25shs0YYZ1sju8UfBvlo57fEdp0RwLEpo=
m7s.live/plugin/snap/v4 v4.0.4/go.mod h1:XHggonZ0lIKEX/OP2rXjV2mPpG4urmvMT3foIaFRsus=
m7s.live/plugin/webrtc/v4 v4.1.3 h1:EQ+PxvFE3DRm6z1Sdmnin1raBAgx80Sv1iiHEoDcZSg=
m7s.live/plugin/webrtc/v4 v4.1.3/go.mod h1:0kDXvyiPUputAR9MT6U9zu8yuJ5JhkYEMHB6aYEBRU8=
m7s.live/plugin/webtransport/v4 v4.0.2 h1:skL5jxg8rJ4PGJZVJ8pZmxRuJHp8i+pXlH6uQzOdEN0=
m7s.live/plugin/webtransport/v4 v4.0.2/go.mod h1:If9KBJmsoz8Ae37+fds4EBinbNV8a8tqPjLGLvVAr28=

42
goreleaser.yml Normal file
View File

@@ -0,0 +1,42 @@
project_name: m7s
archives:
-
files:
- config.yaml
- local.monibuca.com.key
- local.monibuca.com_bundle.pem
builds:
- id: "with-debug"
env: [CGO_ENABLED=0]
ldflags:
- -s -w -X main.version={{.Tag}}
ignore:
- goos: darwin
goarch: arm64
- goos: windows
goarch: arm64
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
hooks:
pre:
- bash -c "debug=1 go generate ./..."
- go mod tidy
- id: "without-debug"
env: [CGO_ENABLED=0]
ldflags:
- -s -w -X main.version={{.Tag}}
goos:
- windows
- darwin
goarch:
- arm64
hooks:
pre:
- bash -c "debug=0 go generate ./..."
- go mod tidy

27
local.monibuca.com.key Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAn2zPOFL8i4EqVwoa7fzlGXXPaTxVoUthlGEuWP5jPZCRctAe
JNPxZT7vhj6PZsRi0jGJKD3KNosprSxN335KzZmm8FPxghRXEt/y7DvKQuuTXyst
eaQoTbZtdX3a2cXNC4aPbDeCCYhrthHrCo8GL07a3lV9FlKpcrmn+zep3+BXHDD4
QiG/6k4/AyZImKIi6de25riP7yIjJgENgXzQvEDRYj/Qkth+2uoMzvjb5dZvDzSF
syZ4haPdNv66UDMFVzsIQ7ZPSILVw91vdQn7yI7sCVdlQT1CgXkbXOHvyZGhVBQh
ikPEDBCQpvR8m45kXaterYtXDrW4AXb/VfbILQIDAQABAoIBAAm3jZfOwxLTsBt3
A7YuvF4FZrtw0H1QxWVQWQ1WkADwH1VipvuyqVt07w99H6txW96Y41W/EmSprcQe
165AGdoXO+wZCbbWe4oseTd523Whuy1JSe/ImCZIcLqBDcZJPpqtvG8poPToyjvi
MrPFjOh0Q3XauxGRvz89XqY6udFp1+jzt7pmUSk9AWi/CYNGWzU32iEgZZNAxhgg
T3f6HkitiyoyqQURuxMxUVdohjdavVQwDrRqwgLM4MkfIhT+1B52xNYhCVzxT5Eg
5VE533fumUULxCK0t8/aneSn4rJ/5+CU0WBDex9cVtDsXCncK9oCkw/PQZO+lzzt
LHzBytcCgYEA1tZISKL2gFb24aDaS/OcHfDkHrFRVQDHP+iWL8TMs5HHUGsM675z
JxYNTgKH4tgg19V7G1N6SfF9wluNYd+4m681DO4kHMdzQ5+RcuLM/p2T8o1hLjf6
CDTjMoSntyecouwbuqmibBzQw834+LR/0h7N12eLgV5MEjdKMLaktqcCgYEAvfiU
FAIePAwSlroJcmL1AVCOYIyoVK9Vg5hEukclDtrzy0KkSKoMsyGrM/ggPvMNL/vz
W6rJSFahoRb8XyDautZVj1oxjYrrVBOKSKLTKr8+ckfztDO6+aljxtxHdv9sRQg1
zQ+NSozgXJSW08Y464I6CPaS5GW1DGTLFAjCeQsCgYAh06WYAkjL1mWTCy+0C8yG
Dlrs1kCXIMM+tdGH/fW5RHfcmq5zJA6fleJMaSuaNSuesFds6wzzPZnuk1nEkmRP
5xt0SL7Y5TKp8CMHstxSLt+PrmEh1OCCkElBuA9sUEligciv8GvJmBPq8LCGAG2r
2PvSMdSObxmNOLVuzCNNOwKBgAo+c02454R5ai8yjPvcFjYh7+uI6jLW2ZelCF+7
ImZwrCDT0SQR92lZcW/1+1cpqBZkUbUpunzqHwEeyjEfBmx4zlhSlsV5LkN0YkqU
bSqq8WUcOCoJeBWqarT4f+oMz/vQ+4W5Rvc0LY0QfimhUMRyW0rMcRNb4K1wafsE
legNAoGADESfqKfuuL2C4+lmwaPb/7K3yirOEaZECKgc5F9YFRFEJ6Wic+8U4Yvn
89cvY8ye1RM4ZDe1GtuzlgRw11kXc65QtjEnj1j8EqESu+3EcZnq/wOzFFbeELFn
kTwlRfbRHELksH04OAphQwa/BFTXPni+zv0tFYBkj0RyXAVumBQ=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,66 @@
-----BEGIN CERTIFICATE-----
MIIGbzCCBNegAwIBAgIQUnjZ1U6EvGqTYJGEoD5X7zANBgkqhkiG9w0BAQwFADBZ
MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg
SW5jLjEjMCEGA1UEAxMaVHJ1c3RBc2lhIFJTQSBEViBUTFMgQ0EgRzIwHhcNMjMw
MjIyMDAwMDAwWhcNMjQwMjIyMjM1OTU5WjAdMRswGQYDVQQDExJsb2NhbC5tb25p
YnVjYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfbM84UvyL
gSpXChrt/OUZdc9pPFWhS2GUYS5Y/mM9kJFy0B4k0/FlPu+GPo9mxGLSMYkoPco2
iymtLE3ffkrNmabwU/GCFFcS3/LsO8pC65NfKy15pChNtm11fdrZxc0Lho9sN4IJ
iGu2EesKjwYvTtreVX0WUqlyuaf7N6nf4FccMPhCIb/qTj8DJkiYoiLp17bmuI/v
IiMmAQ2BfNC8QNFiP9CS2H7a6gzO+Nvl1m8PNIWzJniFo902/rpQMwVXOwhDtk9I
gtXD3W91CfvIjuwJV2VBPUKBeRtc4e/JkaFUFCGKQ8QMEJCm9HybjmRdq16ti1cO
tbgBdv9V9sgtAgMBAAGjggLtMIIC6TAfBgNVHSMEGDAWgBRfOnwREH4MZ3Fh3Iuj
tQADZ/VXHDAdBgNVHQ4EFgQUjqZHMBwnZrrlYVLZnNFBaCgfdU0wDgYDVR0PAQH/
BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMEkGA1UdIARCMEAwNAYLKwYBBAGyMQECAjEwJTAjBggrBgEFBQcCARYXaHR0
cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQIBMH0GCCsGAQUFBwEBBHEwbzBC
BggrBgEFBQcwAoY2aHR0cDovL2NydC50cnVzdC1wcm92aWRlci5jbi9UcnVzdEFz
aWFSU0FEVlRMU0NBRzIuY3J0MCkGCCsGAQUFBzABhh1odHRwOi8vb2NzcC50cnVz
dC1wcm92aWRlci5jbjAdBgNVHREEFjAUghJsb2NhbC5tb25pYnVjYS5jb20wggF/
BgorBgEEAdZ5AgQCBIIBbwSCAWsBaQB3AHb/iD8KtvuVUcJhzPWHujS0pM27Kdxo
Qgqf5mdMWjp0AAABhnkjvQkAAAQDAEgwRgIhAMBG7q1j0b/vZwGD27eUP0+nBh73
P7nC+CSokfpts8SoAiEA+HCrNNZ4jrgiLyi9035m1Hvf414Bn8ksSjWzxNUEiCgA
dgDatr9rP7W2Ip+bwrtca+hwkXFsu1GEhTS9pD0wSNf7qwAAAYZ5I7zdAAAEAwBH
MEUCIQCdIMs1nf5cheetmz4/9om8/6KDsoej8VFXHzaDt4BkAAIgLePqBifr6zUA
lTpl17CRDG7q09kUPzElK8uTLjnugtkAdgDuzdBk1dsazsVct520zROiModGfLzs
3sNRSFlGcR+1mwAAAYZ5I7yiAAAEAwBHMEUCIAbWcfP71joz+2wBVRU78RD0bV6V
ugIiATVYUh+k9duJAiEA7x0JIdFuLBj3ggGH9QpAWdG03z48kZ9Cy1DjibRn4fYw
DQYJKoZIhvcNAQEMBQADggGBAItE6rr+1vFoVA3R71+23W0ctYrBTWyxCOse8i0x
/BEo13FjERXJkKWGSj1mwmTikO94JPcPqm83PVyZ0eIbEPu4IO/E6xFbOkTSQu7c
o+5i7TdqtPfv6AOApt+yBb3t3NHRXk2WYLV4onvuSnorbFaj9wRS7GNr+rXCIbqJ
HaFKpneHoV9XhKYUwdgDr2w6JkGattyFdee5o60+8EtL068Mf6Qg3OmyMosEZuBw
R/Gs4DPVKwxj/qt7cJPZoUDV7L6LzLCkgxre8nvvLbOBkC34i4KQGF4CkTjRPWw+
OpRKOzWIw9fQ2+m+z7QwWi+fZQ31EAT6KGnHqPLePJNj09qqUSff3e/y9szzKGHc
TpKVSCgEuuCiBNze7PG9D8QgBMYHkOuGkMeP1EO0pZ3mxd3obUn+bPz0tsqvMR2t
gBx56pMeFnVNQR26VqT2YE+Xw7j6AQUwLa6SCMEsfPeotnhl5tiIKxjqWjWf1lLV
/YWY7m7yb5ctZnq9FJU926ZLLA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFBzCCA++gAwIBAgIRALIM7VUuMaC/NDp1KHQ76aswDQYJKoZIhvcNAQELBQAw
ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yMjAxMTAwMDAwMDBaFw0y
ODEyMzEyMzU5NTlaMFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEg
VGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIERWIFRM
UyBDQSBHMjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKjGDe0GSaBs
Yl/VhMaTM6GhfR1TAt4mrhN8zfAMwEfLZth+N2ie5ULbW8YvSGzhqkDhGgSBlafm
qq05oeESrIJQyz24j7icGeGyIZ/jIChOOvjt4M8EVi3O0Se7E6RAgVYcX+QWVp5c
Sy+l7XrrtL/pDDL9Bngnq/DVfjCzm5ZYUb1PpyvYTP7trsV+yYOCNmmwQvB4yVjf
IIpHC1OcsPBntMUGeH1Eja4D+qJYhGOxX9kpa+2wTCW06L8T6OhkpJWYn5JYiht5
8exjAR7b8Zi3DeG9oZO5o6Qvhl3f8uGU8lK1j9jCUN/18mI/5vZJ76i+hsgdlfZB
Rh5lmAQjD80M9TY+oD4MYUqB5XrigPfFAUwXFGehhlwCVw7y6+5kpbq/NpvM5Ba8
SeQYUUuMA8RXpTtGlrrTPqJryfa55hTuX/ThhX4gcCVkbyujo0CYr+Uuc14IOyNY
1fD0/qORbllbgV41wiy/2ZUWZQUodqHWkjT1CwIMbQOY5jmrSYGBwwIDAQABo4IB
JjCCASIwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYDVR0OBBYE
FF86fBEQfgxncWHci6O1AANn9VccMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8E
CDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAiBgNVHSAE
GzAZMA0GCysGAQQBsjEBAgIxMAgGBmeBDAECATBDBgNVHR8EPDA6MDigNqA0hjJo
dHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNy
bDA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9k
b2NhLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAHMUom5cxIje2IiFU7mOCsBr2F6CY
eU5cyfQ/Aep9kAXYUDuWsaT85721JxeXFYkf4D/cgNd9+hxT8ZeDOJrn+ysqR7NO
2K9AdqTdIY2uZPKmvgHOkvH2gQD6jc05eSPOwdY/10IPvmpgUKaGOa/tyygL8Og4
3tYyoHipMMnS4OiYKakDJny0XVuchIP7ZMKiP07Q3FIuSS4omzR77kmc75/6Q9dP
v4wa90UCOn1j6r7WhMmX3eT3Gsdj3WMe9bYD0AFuqa6MDyjIeXq08mVGraXiw73s
Zale8OMckn/BU3O/3aFNLHLfET2H2hT6Wb3nwxjpLIfXmSVcVd8A58XH0g==
-----END CERTIFICATE-----

73
main.go
View File

@@ -1,17 +1,74 @@
package main
//go:generate go run gen.go $debug
/*
███ ███ ██████ ███ ██ 👑 ██████ ██ ██ ██████ █████
████ ████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ████ ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██████ ██ ████ ██ ██████ ██████ ██████ ██ ██
The live stream server for Go
(c) dexter 2019-present
说明:
本项目为 monibuca 的启动工程,你也可以自己创建一个启动工程
本启动工程引入了 engine 和一些列官方插件,并且保证版本依赖关系
自己创建工程的时候,版本依赖必须参考本工程,否则容易出现依赖关系错乱
流的播放地址请查看文档https://m7s.live/guide/qa/play.html
推拉流的配置方法看文档https://m7s.live/guide/config.html#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE
高频问题:
1、OBS只能推送 rtmp 协议,如需推送 rtsp 需要安装插件
2、除了rtsp协议以外其他协议播放H265需要使用jessibuca播放器preview 插件内置了jessibuca播放器
3、浏览器不能直接播放rtmp、rtsp等基于tcp的协议因为在js的环境中无法直接使用tcp或者udp传数据js没提供接口而rtsp或rtmp的流是基于tcp或者udp 所以纯web的方式目前是没办法直接播放rtsp或rtmp流的
4、webrtc是否可以播放h265取决于浏览器是否包含h265解码器通常不包含
5、webrtc不支持aac格式的音频
6、gb插件能收到设备的注册但是没有流可能1、媒体端口被防火墙拦截默认是582002、使用公网IP需要配置sipip字段或者mediaip字段用于设备向指定IP发送流。3、配置范围端口部分设备ssrc乱搞导致的
7、当没有订阅者的时候如何自动停止拉流设置publish 配置下的 delayclosetimeout 参数例如 10s代表最后一个订阅者离开后 10s 后自动停止流
8、使用 ffmpeg 推流时请加-c:v h264 -c:a aac否则 ffmpeg 会自动将流转换成系统不支持的格式
9、StreamPath 必须形如 live/test 。不能只有一级,或者斜杠开头,如/live 是错误的。
10、如果遇到直接退出崩溃查看一下fatal.log。
*/
import (
"context"
"flag"
. "github.com/langhuihui/monibuca/monica"
_ "github.com/langhuihui/monibuca/plugins"
"log"
"os"
"fmt"
"m7s.live/engine/v4"
"m7s.live/engine/v4/util"
_ "m7s.live/plugin/logrotate/v4"
_ "m7s.live/plugin/debug/v4"
_ "m7s.live/plugin/gb28181/v4"
_ "m7s.live/plugin/hdl/v4"
_ "m7s.live/plugin/hls/v4"
_ "m7s.live/plugin/hook/v4"
_ "m7s.live/plugin/jessica/v4"
_ "m7s.live/plugin/preview/v4"
_ "m7s.live/plugin/record/v4"
_ "m7s.live/plugin/room/v4"
_ "m7s.live/plugin/rtmp/v4"
_ "m7s.live/plugin/rtsp/v4"
_ "m7s.live/plugin/snap/v4"
_ "m7s.live/plugin/webrtc/v4"
_ "m7s.live/plugin/webtransport/v4"
_ "m7s.live/plugin/edge/v4"
_ "m7s.live/plugin/fmp4/v4"
_ "m7s.live/plugin/monitor/v4"
)
var (
version = "dev"
)
func main() {
log.SetOutput(os.Stdout)
configPath := flag.String("c", "config.toml", "configFile")
fmt.Println("start github.com/langhuihui/monibuca version:", version)
conf := flag.String("c", "config.yaml", "config file")
flag.Parse()
Run(*configPath)
select {}
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), "version", version))
go util.WaitTerm(cancel)
engine.Run(ctx, *conf)
}

BIN
monibuca_windows_amd64.syso Normal file

Binary file not shown.

View File

@@ -1,307 +0,0 @@
package avformat
import (
"errors"
"github.com/langhuihui/monibuca/monica/util"
)
const (
ADTS_HEADER_SIZE = 7
)
// ISO/IEC 14496-15 11(16)/page
//
// Advanced Video Coding
//
type AVCDecoderConfigurationRecord struct {
ConfigurationVersion byte // 8 bits Version
AVCProfileIndication byte // 8 bits
ProfileCompatibility byte // 8 bits
AVCLevelIndication byte // 8 bits
Reserved1 byte // 6 bits
LengthSizeMinusOne byte // 2 bits 非常重要,每个NALU包前面都(lengthSizeMinusOne & 3)+1个字节的NAL包长度描述
Reserved2 byte // 3 bits
NumOfSequenceParameterSets byte // 5 bits SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F
NumOfPictureParameterSets byte // 8 bits PPS 的个数
SequenceParameterSetLength uint16 // 16 byte SPS Length
SequenceParameterSetNALUnit []byte // n byte SPS
PictureParameterSetLength uint16 // 16 byte PPS Length
PictureParameterSetNALUnit []byte // n byte PPS
}
//func (p *AVCDecoderConfigurationRecord) Marshal(b []byte) (n int) {
// b[0] = 1
// b[1] = p.AVCProfileIndication
// b[2] = p.ProfileCompatibility
// b[3] = p.AVCLevelIndication
// b[4] = p.LengthSizeMinusOne | 0xfc
// b[5] = uint8(len(p.SPS)) | 0xe0
// n += 6
//
// for _, sps := range p.SPS {
// pio.PutU16BE(b[n:], uint16(len(sps)))
// n += 2
// copy(b[n:], sps)
// n += len(sps)
// }
//
// b[n] = uint8(len(p.PPS))
// n++
//
// for _, pps := range p.PPS {
// pio.PutU16BE(b[n:], uint16(len(pps)))
// n += 2
// copy(b[n:], pps)
// n += len(pps)
// }
//
// return
//}
var ErrDecconfInvalid = errors.New("decode error")
func (p *AVCDecoderConfigurationRecord) Unmarshal(b []byte) (n int, err error) {
if len(b) < 7 {
err = errors.New("not enough len")
return
}
p.AVCProfileIndication = b[1]
p.ProfileCompatibility = b[2]
p.AVCLevelIndication = b[3]
p.LengthSizeMinusOne = b[4] & 0x03
spscount := int(b[5] & 0x1f)
n += 6
var sps, pps [][]byte
for i := 0; i < spscount; i++ {
if len(b) < n+2 {
err = ErrDecconfInvalid
return
}
spslen := int(util.BigEndian.Uint16(b[n:]))
n += 2
if len(b) < n+spslen {
err = ErrDecconfInvalid
return
}
sps = append(sps, b[n:n+spslen])
n += spslen
}
p.SequenceParameterSetLength = uint16(len(sps[0]))
p.SequenceParameterSetNALUnit = sps[0]
if len(b) < n+1 {
err = ErrDecconfInvalid
return
}
ppscount := int(b[n])
n++
for i := 0; i < ppscount; i++ {
if len(b) < n+2 {
err = ErrDecconfInvalid
return
}
ppslen := int(util.BigEndian.Uint16(b[n:]))
n += 2
if len(b) < n+ppslen {
err = ErrDecconfInvalid
return
}
pps = append(pps, b[n:n+ppslen])
n += ppslen
}
p.PictureParameterSetLength = uint16(len(pps[0]))
p.PictureParameterSetNALUnit = pps[0]
return
}
// ISO/IEC 14496-3 38(52)/page
//
// Audio
//
type AudioSpecificConfig struct {
AudioObjectType byte // 5 bits
SamplingFrequencyIndex byte // 4 bits
ChannelConfiguration byte // 4 bits
GASpecificConfig
}
type GASpecificConfig struct {
FrameLengthFlag byte // 1 bit
DependsOnCoreCoder byte // 1 bit
ExtensionFlag byte // 1 bit
}
//
// AudioObjectTypes -> ISO/IEC 14496-3 43(57)/page
//
// 1 AAC MAIN ISO/IEC 14496-3 subpart 4
// 2 AAC LC ISO/IEC 14496-3 subpart 4
// 3 AAC SSR ISO/IEC 14496-3 subpart 4
// 4 AAC LTP ISO/IEC 14496-3 subpart 4
//
//
// ISO/IEC 13838-7 20(25)/page
//
// Advanced Audio Coding
//
// AudioDataTransportStream
type ADTS struct {
ADTSFixedHeader
ADTSVariableHeader
}
// 28 bits
type ADTSFixedHeader struct {
SyncWord uint16 // 12 bits The bit string 1111 1111 1111. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
ID byte // 1 bit MPEG identifier, set to 1. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
Layer byte // 2 bits Indicates which layer is used. Set to 00. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
ProtectionAbsent byte // 1 bit Indicates whether error_check() data is present or not. Same assyntax element protection_bit in ISO/IEC 11172-3,subclause 2.4.1 and 2.4.2 (Table 8)
Profile byte // 2 bits profile used. See clause 2 (Table 8)
SamplingFrequencyIndex byte // 4 bits indicates the sampling frequency used according to the followingtable (Table 8)
PrivateBit byte // 1 bit see ISO/IEC 11172-3, subclause 2.4.2.3 (Table 8)
ChannelConfiguration byte // 3 bits indicates the channel configuration used. Ifchannel_configuration is greater than 0, the channelconfiguration is given in Table 42, see subclause 8.5.3.1. Ifchannel_configuration equals 0, the channel configuration is notspecified in the header and must be given by aprogram_config_element() following as first syntactic element inthe first raw_data_block() after the header (seesubclause 8.5.3.2), or by the implicit configuration (seesubclause 8.5.3.3) or must be known in the application (Table 8)
OriginalCopy byte // 1 bit see ISO/IEC 11172-3, definition of data element copyright
Home byte // 1 bit see ISO/IEC 11172-3, definition of data element original/copy
}
// SyncWord, 同步头 总是0xFFF, all bits must be 1代表着一个ADTS帧的开始
// ID, MPEG Version: 0 for MPEG-4, 1 for MPEG-2
// Layer, always: '00'
// ProtectionAbsent, 表示是否误码校验
// Profile, 表示使用哪个级别的AAC有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种.
// SamplingFrequencyIndex, 表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值
// PrivateBit,
// ChannelConfiguration, 表示声道数
// OriginalCopy,
// Home,
// Profile:
//
// 0: Main profile
// 1: Low Complexity profile(LC)
// 2: Scalable Sampling Rate profile(SSR)
// 3: Reserved
//
var SamplingFrequencies = [...]int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}
// Sampling Frequencies[]:
//
// 0: 96000 Hz
// 1: 88200 Hz
// 2: 64000 Hz
// 3: 48000 Hz
// 4: 44100 Hz
// 5: 32000 Hz
// 6: 24000 Hz
// 7: 22050 Hz
// 8: 16000 Hz
// 9: 12000 Hz
// 10: 11025 Hz
// 11: 8000 Hz
// 12: 7350 Hz
// 13: Reserved
// 14: Reserved
// 15: frequency is written explictly
//
// ChannelConfiguration:
//
// 0: Defined in AOT Specifc Config
// 1: 1 channel: front-center
// 2: 2 channels: front-left, front-right
// 3: 3 channels: front-center, front-left, front-right
// 4: 4 channels: front-center, front-left, front-right, back-center
// 5: 5 channels: front-center, front-left, front-right, back-left, back-right
// 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
// 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
// 8-15: Reserved
//
// 28 bits
type ADTSVariableHeader struct {
CopyrightIdentificationBit byte // 1 bit One bit of the 72-bit copyright identification field (seecopyright_id above). The bits of this field are transmitted frame by frame; the first bit is indicated by the copyright_identification_start bit set to 1. The field consists of an 8-bit copyright_identifier, followed by a 64-bit copyright_number.The copyright identifier is given by a Registration Authority as designated by SC29. The copyright_number is a value which identifies uniquely the copyrighted material. See ISO/IEC 13818-3, subclause 2.5.2.13 (Table 9)
CopyrightIdentificationStart byte // 1 bit One bit to indicate that the copyright_identification_bit in this audio frame is the first bit of the 72-bit copyright identification. If no copyright identification is transmitted, this bit should be kept '0'.'0' no start of copyright identification in this audio frame '1' start of copyright identification in this audio frame See ISO/IEC 13818-3, subclause 2.5.2.13 (Table 9)
AACFrameLength uint16 // 13 bits Length of the frame including headers and error_check in bytes(Table 9)
ADTSBufferFullness uint16 // 11 bits state of the bit reservoir in the course of encoding the ADTS frame, up to and including the first raw_data_block() and the optionally following adts_raw_data_block_error_check(). It is transmitted as the number of available bits in the bit reservoir divided by NCC divided by 32 and truncated to an integer value (Table 9). A value of hexadecimal 7FF signals that the bitstream is a variable rate bitstream. In this case, buffer fullness is not applicable
NumberOfRawDataBlockInFrame byte // 2 bits Number of raw_data_block()s that are multiplexed in the adts_frame() is equal to number_of_raw_data_blocks_in_frame + 1. The minimum value is 0 indicating 1 raw_data_block()(Table 9)
}
// CopyrightIdentificationBit,
// CopyrightIdentificationStart,
// AACFrameLength, 一个ADTS帧的长度包括ADTS头和raw data block.
// ADTSBufferFullness, 0x7FF 说明是码率可变的码流.
// NumberOfRawDataBlockInFrame, 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
// 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
func ADTSToAudioSpecificConfig(data []byte) []byte {
profile := ((data[2] & 0xc0) >> 6) + 1
sampleRate := (data[2] & 0x3c) >> 2
channel := ((data[2] & 0x1) << 2) | ((data[3] & 0xc0) >> 6)
config1 := (profile << 3) | ((sampleRate & 0xe) >> 1)
config2 := ((sampleRate & 0x1) << 7) | (channel << 3)
return []byte{0xAF, 0x00, config1, config2}
}
func AudioSpecificConfigToADTS(asc AudioSpecificConfig, rawDataLength int) (adts ADTS, adtsByte []byte, err error) {
if asc.ChannelConfiguration > 8 || asc.FrameLengthFlag > 13 {
err = errors.New("Reserved field.")
return
}
// ADTSFixedHeader
adts.SyncWord = 0xfff
adts.ID = 0
adts.Layer = 0
adts.ProtectionAbsent = 1
// SyncWord(12) + ID(1) + Layer(2) + ProtectionAbsent(1)
adtsByte = append(adtsByte, 0xff)
adtsByte = append(adtsByte, 0xf1)
if asc.AudioObjectType >= 3 || asc.AudioObjectType == 0 {
adts.Profile = 1
} else {
adts.Profile = asc.AudioObjectType - 1
}
adts.SamplingFrequencyIndex = asc.SamplingFrequencyIndex
adts.PrivateBit = 0
adts.ChannelConfiguration = asc.ChannelConfiguration
adts.OriginalCopy = 0
adts.Home = 0
// Profile(2) + SamplingFrequencyIndex(4) + PrivateBit(1) + ChannelConfiguration(3)(取高1位)
byte3 := uint8(adts.Profile<<6) + uint8(adts.SamplingFrequencyIndex<<2) + uint8(adts.PrivateBit<<1) + uint8((adts.ChannelConfiguration&0x7)>>2)
adtsByte = append(adtsByte, byte3)
// ADTSVariableHeader
adts.CopyrightIdentificationBit = 0
adts.CopyrightIdentificationStart = 0
adts.AACFrameLength = 7 + uint16(rawDataLength)
adts.ADTSBufferFullness = 0x7ff
adts.NumberOfRawDataBlockInFrame = 0
// ChannelConfiguration(3)(取低2位) + OriginalCopy(1) + Home(1) + CopyrightIdentificationBit(1) + CopyrightIdentificationStart(1) + AACFrameLength(13)(取高2位)
byte4 := uint8((adts.ChannelConfiguration&0x3)<<6) + uint8((adts.AACFrameLength&0x1fff)>>11)
adtsByte = append(adtsByte, byte4)
// AACFrameLength(13)
// xx xxxxxxxx xxx
// 取中间的部分
byte5 := uint8(((adts.AACFrameLength & 0x1fff) >> 3) & 0x0ff)
adtsByte = append(adtsByte, byte5)
// AACFrameLength(13)(取低3位) + ADTSBufferFullness(11)(取高5位)
byte6 := uint8((adts.AACFrameLength&0x0007)<<5) + 0x1f
adtsByte = append(adtsByte, byte6)
// ADTSBufferFullness(11)(取低6位) + NumberOfRawDataBlockInFrame(2)
adtsByte = append(adtsByte, 0xfc)
return
}

View File

@@ -1,114 +0,0 @@
package avformat
import (
"github.com/langhuihui/monibuca/monica/pool"
"github.com/langhuihui/monibuca/monica/util"
"io"
)
const (
// FLV Tag Type
FLV_TAG_TYPE_AUDIO = 0x08
FLV_TAG_TYPE_VIDEO = 0x09
FLV_TAG_TYPE_SCRIPT = 0x12
)
var (
// 音频格式. 4 bit
SoundFormat = map[byte]string{
0: "Linear PCM, platform endian",
1: "ADPCM",
2: "MP3",
3: "Linear PCM, little endian",
4: "Nellymoser 16kHz mono",
5: "Nellymoser 8kHz mono",
6: "Nellymoser",
7: "G.711 A-law logarithmic PCM",
8: "G.711 mu-law logarithmic PCM",
9: "reserved",
10: "AAC",
11: "Speex",
14: "MP3 8Khz",
15: "Device-specific sound"}
// 采样频率. 2 bit
SoundRate = map[byte]int{
0: 5500,
1: 11000,
2: 22000,
3: 44000}
// 量化精度. 1 bit
SoundSize = map[byte]string{
0: "8Bit",
1: "16Bit"}
// 音频类型. 1bit
SoundType = map[byte]string{
0: "Mono",
1: "Stereo"}
// 视频帧类型. 4bit
FrameType = map[byte]string{
1: "keyframe (for AVC, a seekable frame)",
2: "inter frame (for AVC, a non-seekable frame)",
3: "disposable inter frame (H.263 only)",
4: "generated keyframe (reserved for server use only)",
5: "video info/command frame"}
// 视频编码类型. 4bit
CodecID = map[byte]string{
1: "JPEG (currently unused)",
2: "Sorenson H.263",
3: "Screen video",
4: "On2 VP6",
5: "On2 VP6 with alpha channel",
6: "Screen video version 2",
7: "AVC",
12: "H265"}
)
var FLVHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0, 0, 0, 9, 0, 0, 0, 0}
func WriteFLVTag(w io.Writer, tag *pool.SendPacket) (err error) {
head := pool.GetSlice(11)
defer pool.RecycleSlice(head)
tail := pool.GetSlice(4)
defer pool.RecycleSlice(tail)
head[0] = tag.Packet.Type
dataSize := uint32(len(tag.Packet.Payload))
util.BigEndian.PutUint32(tail, dataSize+11)
util.BigEndian.PutUint24(head[1:], dataSize)
util.BigEndian.PutUint24(head[4:], tag.Timestamp)
util.BigEndian.PutUint32(head[7:], 0)
if _, err = w.Write(head); err != nil {
return
}
// Tag Data
if _, err = w.Write(tag.Packet.Payload); err != nil {
return
}
if _, err = w.Write(tail); err != nil { // PreviousTagSizeN(4)
return
}
return
}
func ReadFLVTag(r io.Reader) (tag *pool.AVPacket, err error) {
head := pool.GetSlice(11)
defer pool.RecycleSlice(head)
if _, err = io.ReadFull(r, head); err != nil {
return
}
tag = pool.NewAVPacket(head[0])
dataSize := util.BigEndian.Uint24(head[1:])
tag.Timestamp = util.BigEndian.Uint24(head[4:])
body := pool.GetSlice(int(dataSize))
defer pool.RecycleSlice(body)
if _, err = io.ReadFull(r, body); err == nil {
tag.Payload = body
t := pool.GetSlice(4)
_, err = io.ReadFull(r, t)
pool.RecycleSlice(t)
}
return
}

View File

@@ -1,144 +0,0 @@
package avformat
import (
"io"
)
// Start Code + NAL Unit -> NALU Header + NALU Body
// RTP Packet -> NALU Header + NALU Body
// NALU Body -> Slice Header + Slice data
// Slice data -> flags + Macroblock layer1 + Macroblock layer2 + ...
// Macroblock layer1 -> mb_type + PCM Data
// Macroblock layer2 -> mb_type + Sub_mb_pred or mb_pred + Residual Data
// Residual Data ->
const (
// NALU Type
NALU_Unspecified = 0
NALU_Non_IDR_Picture = 1
NALU_Data_Partition_A = 2
NALU_Data_Partition_B = 3
NALU_Data_Partition_C = 4
NALU_IDR_Picture = 5
NALU_SEI = 6
NALU_SPS = 7
NALU_PPS = 8
NALU_Access_Unit_Delimiter = 9
NALU_Sequence_End = 10
NALU_Stream_End = 11
NALU_Filler_Data = 12
NALU_SPS_Extension = 13
NALU_Prefix = 14
NALU_SPS_Subset = 15
NALU_DPS = 16
NALU_Reserved1 = 17
NALU_Reserved2 = 18
NALU_Not_Auxiliary_Coded = 19
NALU_Coded_Slice_Extension = 20
NALU_Reserved3 = 21
NALU_Reserved4 = 22
NALU_Reserved5 = 23
NALU_NotReserved = 24
// 24 - 31 NALU_NotReserved
)
var (
NALU_AUD_BYTE = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xF0}
NALU_Delimiter1 = []byte{0x00, 0x00, 0x01}
NALU_Delimiter2 = []byte{0x00, 0x00, 0x00, 0x01}
RTMP_AVC_HEAD = []byte{0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x1E, 0xFF}
RTMP_KEYFRAME_HEAD = []byte{0x17, 0x01, 0x00, 0x00, 0x00}
RTMP_NORMALFRAME_HEAD = []byte{0x27, 0x01, 0x00, 0x00, 0x00}
)
var NALU_SEI_BYTE []byte
// H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)
// NAL - Network Abstract Layer
// raw byte sequence payload (RBSP) 原始字节序列载荷
type H264 struct {
}
type NALUnit struct {
NALUHeader
RBSP
}
type NALUHeader struct {
forbidden_zero_bit byte // 1 bit 0
nal_ref_idc byte // 2 bits nal_unit_type等于6,9,10,11或12的NAL单元其nal_ref_idc都应等于 0
nal_uint_type byte // 5 bits 包含在 NAL 单元中的 RBSP 数据结构的类型
}
type RBSP interface {
}
/*
0 Unspecified non-VCL
1 Coded slice of a non-IDR picture VCL
2 Coded slice data partition A VCL
3 Coded slice data partition B VCL
4 Coded slice data partition C VCL
5 Coded slice of an IDR picture VCL
6 Supplemental enhancement information (SEI) non-VCL
7 Sequence parameter set non-VCL
8 Picture parameter set non-VCL
9 Access unit delimiter non-VCL
10 End of sequence non-VCL
11 End of stream non-VCL
12 Filler data non-VCL
13 Sequence parameter set extension non-VCL
14 Prefix NAL unit non-VCL
15 Subset sequence parameter set non-VCL
16 Depth parameter set non-VCL
17..18 Reserved non-VCL
19 Coded slice of an auxiliary coded picture without partitioning non-VCL
20 Coded slice extension non-VCL
21 Coded slice extension for depth view components non-VCL
22..23 Reserved non-VCL
24..31 Unspecified non-VCL
0:未规定
1:非IDR图像中不采用数据划分的片段
2:非IDR图像中A类数据划分片段
3:非IDR图像中B类数据划分片段
4:非IDR图像中C类数据划分片段
5:IDR图像的片段
6:补充增强信息SEI
7:序列参数集SPS
8:图像参数集PPS
9:分割符
10:序列结束符
11:流结束符
12:填充数据
13:序列参数集扩展
14:带前缀的NAL单元
15:子序列参数集
16 18:保留
19:不采用数据划分的辅助编码图像片段
20:编码片段扩展
21 23:保留
24 31:未规定
nal_unit_type NAL类型 nal_reference_bit
0 未使用 0
1 非IDR的片 此片属于参考帧,则不等于0,不属于参考帧则等与0
2 片数据A分区 同上
3 片数据B分区 同上
4 片数据C分区 同上
5 IDR图像的片 5
6 补充增强信息单元SEI 0
7 序列参数集 非0
8 图像参数集 非0
9 分界符 0
10 序列结束 0
11 码流结束 0
12 填充 0
13..23 保留 0
24..31 不保留 0
*/
func ReadPPS(w io.Writer) {
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,577 +0,0 @@
package mpegts
import (
"bytes"
"errors"
"github.com/langhuihui/monibuca/monica/util"
"io"
"io/ioutil"
//"sync"
)
// NALU AUD 00 00 00 01 09 F0
const (
TS_PACKET_SIZE = 188
TS_DVHS_PACKET_SIZE = 192
TS_FEC_PACKET_SIZE = 204
TS_MAX_PACKET_SIZE = 204
PID_PAT = 0x0000
PID_CAT = 0x0001
PID_TSDT = 0x0002
PID_RESERVED1 = 0x0003
PID_RESERVED2 = 0x000F
PID_NIT_ST = 0x0010
PID_SDT_BAT_ST = 0x0011
PID_EIT_ST = 0x0012
PID_RST_ST = 0x0013
PID_TDT_TOT_ST = 0x0014
PID_NET_SYNC = 0x0015
PID_RESERVED3 = 0x0016
PID_RESERVED4 = 0x001B
PID_SIGNALLING = 0x001C
PID_MEASURE = 0x001D
PID_DIT = 0x001E
PID_SIT = 0x001F
// 0x0003 - 0x000F Reserved
// 0x0010 - 0x1FFE May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes
// 0x1FFF Null Packet
// program_association_section
// conditional_access_section
// TS_program_map_section
// TS_description_section
// ISO_IEC_14496_scene_description_section
// ISO_IEC_14496_object_descriptor_section
// Metadata_section
// IPMP_Control_Information_section (defined in ISO/IEC 13818-11)
TABLE_PAS = 0x00
TABLE_CAS = 0x01
TABLE_TSPMS = 0x02
TABLE_TSDS = 0x03
TABLE_ISO_IEC_14496_SDC = 0x04
TABLE_ISO_IEC_14496_ODC = 0x05
TABLE_MS = 0x06
TABLE_IPMP_CIS = 0x07
// 0x06 - 0x37 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved
// 0x38 - 0x3F Defined in ISO/IEC 13818-6
// 0x40 - 0xFE User private
// 0xFF Forbidden
STREAM_TYPE_H264 = 0x1B
STREAM_TYPE_AAC = 0x0F
// 1110 xxxx
// 110x xxxx
STREAM_ID_VIDEO = 0xE0 // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC14496-2 video stream number xxxx
STREAM_ID_AUDIO = 0xC0 // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC14496-3 audio stream number x xxxx
PAT_PKT_TYPE = 0
PMT_PKT_TYPE = 1
PES_PKT_TYPE = 2
)
//
// MPEGTS -> PAT + PMT + PES
// ES -> PES -> TS
//
type MpegTsStream struct {
firstTsPkt *MpegTsPacket // 每一帧的第一个TS包
patPkt *MpegTsPacket // 装载PAT的TS包
pmtPkt *MpegTsPacket // 装载PMT的TS包
pat *MpegTsPAT // PAT表信息
pmt *MpegTsPMT // PMT表信息
closed bool //是否已经关闭
TsPesPktChan chan *MpegTsPesStream // TS + PES Packet Channel,将封装的每一帧ES数据,通过channel来传输
}
func NewMpegTsStream(bufferLength int) (ts *MpegTsStream) {
ts = new(MpegTsStream)
ts.firstTsPkt = new(MpegTsPacket)
ts.patPkt = new(MpegTsPacket)
ts.pmtPkt = new(MpegTsPacket)
ts.pat = new(MpegTsPAT)
ts.pmt = new(MpegTsPMT)
ts.TsPesPktChan = make(chan *MpegTsPesStream, bufferLength)
return
}
// ios13818-1-CN.pdf 33/165
//
// TS
//
// Packet == Header + Payload == 188 bytes
type MpegTsPacket struct {
Header MpegTsHeader
Payload []byte
}
// 前面32bit的数据即TS分组首部,它指出了这个分组的属性
type MpegTsHeader struct {
SyncByte byte // 8 bits 同步字节,固定为0x47,表示后面是一个TS分组
TransportErrorIndicator byte // 1 bit 传输错误标志位
PayloadUnitStartIndicator byte // 1 bit 负载单元开始标志(packet不满188字节时需填充).为1时,表示在4个字节后,有一个调整字节
TransportPriority byte // 1 bit 传输优先级
Pid uint16 // 13 bits Packet ID号码,唯一的号码对应不同的包.为0表示携带的是PAT表
TransportScramblingControl byte // 2 bits 加密标志位(00:未加密;其他表示已加密)
AdaptionFieldControl byte // 2 bits 附加区域控制.表示TS分组首部后面是否跟随有调整字段和有效负载.01仅含有效负载(没有adaptation_field),10仅含调整字段(没有Payload),11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload).为00的话解码器不进行处理.空分组没有调整字段
ContinuityCounter byte // 4 bits 包递增计数器.范围0-15,具有相同的PID的TS分组传输时每次加1,到15后清0.不过,有些情况下是不计数的.
MpegTsHeaderAdaptationField
}
// 调整字段,只可能出现在每一帧的开头(当含有pcr的时候),或者结尾(当不满足188个字节的时候)
// adaptionFieldControl 00 -> 高字节代表调整字段, 低字节代表负载字段 0x20 0x10
// PCR字段编码在MPEG-2 TS包的自适应字段(Adaptation field)的6个Byte中,其中6 bits为预留位,42 bits为有效位()
// MpegTsHeaderAdaptationField + stuffing bytes
type MpegTsHeaderAdaptationField struct {
AdaptationFieldLength byte // 8bits 本区域除了本字节剩下的长度(不包含本字节!!!切记), if adaptationFieldLength > 0, 那么就有下面8个字段. adaptation_field_length 值必须在 0 到 182 的区间内.当 adaptation_field_control 值为'10'时,adaptation_field_length 值必须为 183
DiscontinuityIndicator byte // 1bit 置于"1"时,指示当前传输流包的不连续性状态为真.当 discontinuity_indicator 设置为"0"或不存在时,不连续性状态为假.不连续性指示符用于指示两种类型的不连续性,系统时间基不连续性和 continuity_counter 不连续性.
RandomAccessIndicator byte // 1bit 指示当前的传输流包以及可能的具有相同 PID 的后续传输流包,在此点包含有助于随机接入的某些信息.特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点.此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在
ElementaryStreamPriorityIndicator byte // 1bit 在具有相同 PID 的包之间,它指示此传输流包有效载荷内承载的基本流数据的优先级.1->指示该有效载荷具有比其他传输流包有效载荷更高的优先级
PCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 PCR 字段.0->指示自适应字段不包含任何 PCR 字段
OPCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 OPCR字段.0->指示自适应字段不包含任何 OPCR 字段
SplicingPointFlag byte // 1bit 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现.0->指示自适应字段中 splice_countdown 字段不存在
TrasportPrivateDataFlag byte // 1bit 1->指示自适应字段包含一个或多个 private_data 字节.0->指示自适应字段不包含任何 private_data 字节
AdaptationFieldExtensionFlag byte // 1bit 1->指示自适应字段扩展的存在.0->指示自适应字段中自适应字段扩展不存在
// Optional Fields
ProgramClockReferenceBase uint64 // 33 bits pcr
Reserved1 byte // 6 bits
ProgramClockReferenceExtension uint16 // 9 bits
OriginalProgramClockReferenceBase uint64 // 33 bits opcr
Reserved2 byte // 6 bits
OriginalProgramClockReferenceExtension uint16 // 9 bits
SpliceCountdown byte // 8 bits
TransportPrivateDataLength byte // 8 bits 指定紧随传输private_data_length 字段的 private_data 字节数. private_data 字节数不能使专用数据扩展超出自适应字段的范围
PrivateDataByte byte // 8 bits 不通过 ITU-T|ISO/IEC 指定
AdaptationFieldExtensionLength byte // 8 bits 指定紧随此字段的扩展的自适应字段数据的字节数,包括要保留的字节(如果存在)
LtwFlag byte // 1 bit 1->指示 ltw_offset 字段存在
PiecewiseRateFlag byte // 1 bit 1->指示 piecewise_rate 字段存在
SeamlessSpliceFlag byte // 1 bit 1->指示 splice_type 以及 DTS_next_AU 字段存在. 0->指示无论是 splice_type 字段还是 DTS_next_AU 字段均不存在
// Optional Fields
LtwValidFlag byte // 1 bit 1->指示 ltw_offset 的值必将生效.0->指示 ltw_offset 字段中该值未定义
LtwOffset uint16 // 15 bits 其值仅当 ltw_valid 标志字段具有'1'值时才定义.定义时,法定时间窗补偿以(300/fs)秒为度量单位,其中 fs 为此 PID 所归属的节目的系统时钟频率
Reserved3 byte // 2 bits 保留
PiecewiseRate uint32 // 22 bits 只要当 ltw_flag 和 ltw_valid_flag 均置于1时,此 22 比特字段的含义才确定
SpliceType byte // 4 bits
DtsNextAU uint64 // 33 bits (解码时间标记下一个存取单元)
// stuffing bytes
// 此为固定的 8 比特值等于'1111 1111',能够通过编码器插入.它亦能被解码器丢弃
}
// ios13818-1-CN.pdf 77
//
// Descriptor
//
type MpegTsDescriptor struct {
Tag byte // 8 bits 标识每一个描述符
Length byte // 8 bits 指定紧随 descriptor_length 字段的描述符的字节数
Data []byte
}
func ReadTsPacket(r io.Reader) (packet MpegTsPacket, err error) {
lr := &io.LimitedReader{R: r, N: TS_PACKET_SIZE}
// header
packet.Header, err = ReadTsHeader(lr)
if err != nil {
return
}
// payload
packet.Payload = make([]byte, lr.N)
_, err = lr.Read(packet.Payload)
if err != nil {
return
}
return
}
func ReadTsHeader(r io.Reader) (header MpegTsHeader, err error) {
var h uint32
// MPEGTS Header 4个字节
h, err = util.ReadByteToUint32(r, true)
if err != nil {
return
}
// payloadUnitStartIndicator
// 为1时,表示在4个字节后,有一个调整字节.包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
// header.payloadUnitStartIndicator = uint8(h & 0x400000)
// | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 |
if (h&0xff000000)>>24 != 0x47 {
err = errors.New("mpegts header sync error!")
return
}
// | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 |
header.SyncByte = byte((h & 0xff000000) >> 24)
// | 0000 0000 | 1000 0000 | 0000 0000 | 0000 0000 |
header.TransportErrorIndicator = byte((h & 0x800000) >> 23)
// | 0000 0000 | 0100 0000 | 0000 0000 | 0000 0000 |
header.PayloadUnitStartIndicator = byte((h & 0x400000) >> 22)
// | 0000 0000 | 0010 0000 | 0000 0000 | 0000 0000 |
header.TransportPriority = byte((h & 0x200000) >> 21)
// | 0000 0000 | 0001 1111 | 1111 1111 | 0000 0000 |
header.Pid = uint16((h & 0x1fff00) >> 8)
// | 0000 0000 | 0000 0000 | 0000 0000 | 1100 0000 |
header.TransportScramblingControl = byte((h & 0xc0) >> 6)
// | 0000 0000 | 0000 0000 | 0000 0000 | 0011 0000 |
// 0x30 , 0x20 -> adaptation_field, 0x10 -> Payload
header.AdaptionFieldControl = byte((h & 0x30) >> 4)
// | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1111 |
header.ContinuityCounter = byte(h & 0xf)
// | 0010 0000 |
// adaptionFieldControl
// 表示TS分组首部后面是否跟随有调整字段和有效负载.
// 01仅含有效负载(没有adaptation_field)
// 10仅含调整字段(没有Payload)
// 11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload).
// 为00的话解码器不进行处理.空分组没有调整字段
// 当值为'11时,adaptation_field_length 值必须在0 到182 的区间内.
// 当值为'10'时,adaptation_field_length 值必须为183.
// 对于承载PES 包的传输流包,只要存在欠充足的PES 包数据就需要通过填充来完全填满传输流包的有效载荷字节.
// 填充通过规定自适应字段长度比自适应字段中数据元的长度总和还要长来实现,以致于自适应字段在完全容纳有效的PES 包数据后,有效载荷字节仍有剩余.自适应字段中额外空间采用填充字节填满.
if header.AdaptionFieldControl >= 2 {
// adaptationFieldLength
header.AdaptationFieldLength, err = util.ReadByteToUint8(r)
if err != nil {
return
}
if header.AdaptationFieldLength > 0 {
lr := &io.LimitedReader{R: r, N: int64(header.AdaptationFieldLength)}
// discontinuityIndicator(1)
// randomAccessIndicator(1)
// elementaryStreamPriorityIndicator
// PCRFlag
// OPCRFlag
// splicingPointFlag
// trasportPrivateDataFlag
// adaptationFieldExtensionFlag
var flags uint8
flags, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
header.DiscontinuityIndicator = flags & 0x80
header.RandomAccessIndicator = flags & 0x40
header.ElementaryStreamPriorityIndicator = flags & 0x20
header.PCRFlag = flags & 0x10
header.OPCRFlag = flags & 0x08
header.SplicingPointFlag = flags & 0x04
header.TrasportPrivateDataFlag = flags & 0x02
header.AdaptationFieldExtensionFlag = flags & 0x01
// randomAccessIndicator
// 在此点包含有助于随机接入的某些信息.
// 特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点.
// 此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在
if header.RandomAccessIndicator != 0 {
}
// PCRFlag
// 1->指示 adaptation_field 包含以两部分编码的 PCR 字段.
// 0->指示自适应字段不包含任何 PCR 字段
if header.PCRFlag != 0 {
var pcr uint64
pcr, err = util.ReadByteToUint48(lr, true)
if err != nil {
return
}
// PCR(i) = PCR_base(i)*300 + PCR_ext(i)
// afd.programClockReferenceBase * 300 + afd.programClockReferenceExtension
header.ProgramClockReferenceBase = pcr >> 15 // 9 bits + 6 bits
header.ProgramClockReferenceExtension = uint16(pcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 |
}
// OPCRFlag
if header.OPCRFlag != 0 {
var opcr uint64
opcr, err = util.ReadByteToUint48(lr, true)
if err != nil {
return
}
// OPCR(i) = OPCR_base(i)*300 + OPCR_ext(i)
// afd.originalProgramClockReferenceBase * 300 + afd.originalProgramClockReferenceExtension
header.OriginalProgramClockReferenceBase = opcr >> 15 // 9 bits + 6 bits
header.OriginalProgramClockReferenceExtension = uint16(opcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 |
}
// splicingPointFlag
// 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现.
// 0->指示自适应字段中 splice_countdown 字段不存在
if header.SplicingPointFlag != 0 {
header.SpliceCountdown, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
}
// trasportPrivateDataFlag
// 1->指示自适应字段包含一个或多个 private_data 字节.
// 0->指示自适应字段不包含任何 private_data 字节
if header.TrasportPrivateDataFlag != 0 {
header.TransportPrivateDataLength, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
// privateDataByte
b := make([]byte, header.TransportPrivateDataLength)
if _, err = lr.Read(b); err != nil {
return
}
}
// adaptationFieldExtensionFlag
if header.AdaptationFieldExtensionFlag != 0 {
}
// 消耗掉剩下的数据,我们不关心
if lr.N > 0 {
// Discard 是一个 io.Writer,对它进行的任何 Write 调用都将无条件成功
// 但是ioutil.Discard不记录copy得到的数值
// 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据
if _, err = io.CopyN(ioutil.Discard, lr, int64(lr.N)); err != nil {
return
}
}
}
}
return
}
func WriteTsHeader(w io.Writer, header MpegTsHeader) (written int, err error) {
if header.SyncByte != 0x47 {
err = errors.New("mpegts header sync error!")
return
}
h := uint32(header.SyncByte)<<24 + uint32(header.TransportErrorIndicator)<<23 + uint32(header.PayloadUnitStartIndicator)<<22 + uint32(header.TransportPriority)<<21 + uint32(header.Pid)<<8 + uint32(header.TransportScramblingControl)<<6 + uint32(header.AdaptionFieldControl)<<4 + uint32(header.ContinuityCounter)
if err = util.WriteUint32ToByte(w, h, true); err != nil {
return
}
written += 4
if header.AdaptionFieldControl >= 2 {
// adaptationFieldLength(8)
if err = util.WriteUint8ToByte(w, header.AdaptationFieldLength); err != nil {
return
}
written += 1
if header.AdaptationFieldLength > 0 {
// discontinuityIndicator(1)
// randomAccessIndicator(1)
// elementaryStreamPriorityIndicator(1)
// PCRFlag(1)
// OPCRFlag(1)
// splicingPointFlag(1)
// trasportPrivateDataFlag(1)
// adaptationFieldExtensionFlag(1)
threeIndicatorFiveFlags := uint8(header.DiscontinuityIndicator<<7) + uint8(header.RandomAccessIndicator<<6) + uint8(header.ElementaryStreamPriorityIndicator<<5) + uint8(header.PCRFlag<<4) + uint8(header.OPCRFlag<<3) + uint8(header.SplicingPointFlag<<2) + uint8(header.TrasportPrivateDataFlag<<1) + uint8(header.AdaptationFieldExtensionFlag)
if err = util.WriteUint8ToByte(w, threeIndicatorFiveFlags); err != nil {
return
}
written += 1
// PCR(i) = PCR_base(i)*300 + PCR_ext(i)
if header.PCRFlag != 0 {
pcr := header.ProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.ProgramClockReferenceExtension)
if err = util.WriteUint48ToByte(w, pcr, true); err != nil {
return
}
written += 6
}
// OPCRFlag
if header.OPCRFlag != 0 {
opcr := header.OriginalProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.OriginalProgramClockReferenceExtension)
if err = util.WriteUint48ToByte(w, opcr, true); err != nil {
return
}
written += 6
}
}
}
return
}
//
//func (s *MpegTsStream) TestWrite(fileName string) error {
//
// if fileName != "" {
// file, err := os.Create(fileName)
// if err != nil {
// panic(err)
// }
// defer file.Close()
//
// patTsHeader := []byte{0x47, 0x40, 0x00, 0x10}
//
// if err := WritePATPacket(file, patTsHeader, *s.pat); err != nil {
// panic(err)
// }
//
// // TODO:这里的pid应该是由PAT给的
// pmtTsHeader := []byte{0x47, 0x41, 0x00, 0x10}
//
// if err := WritePMTPacket(file, pmtTsHeader, *s.pmt); err != nil {
// panic(err)
// }
// }
//
// var videoFrame int
// var audioFrame int
// for {
// tsPesPkt, ok := <-s.TsPesPktChan
// if !ok {
// fmt.Println("frame index, video , audio :", videoFrame, audioFrame)
// break
// }
//
// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_AUDIO {
// audioFrame++
// }
//
// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_VIDEO {
// println(tsPesPkt.PesPkt.Header.Pts)
// videoFrame++
// }
//
// fmt.Sprintf("%s", tsPesPkt)
//
// // if err := WritePESPacket(file, tsPesPkt.TsPkt.Header, tsPesPkt.PesPkt); err != nil {
// // return err
// // }
//
// }
//
// return nil
//}
func (s *MpegTsStream) readPAT(packet *MpegTsPacket, pr io.Reader) (err error) {
// 首先找到PID==0x00的TS包(PAT)
if PID_PAT == packet.Header.Pid {
if len(packet.Payload) == 188 {
pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff}
}
// Header + PSI + Paylod
pat, err := ReadPAT(pr)
if err != nil {
return err
}
s.pat = &pat
s.patPkt = packet
}
return
}
func (s *MpegTsStream) readPMT(packet *MpegTsPacket, pr io.Reader) (err error) {
// 在读取PAT中已经将所有频道节目信息(PMT_PID)保存了起来
// 接着读取所有TS包里面的PID,找出PID==PMT_PID的TS包,就是PMT表
for _, v := range s.pat.Program {
if v.ProgramMapPID == packet.Header.Pid {
if len(packet.Payload) == 188 {
pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff}
}
// Header + PSI + Paylod
pmt, err := ReadPMT(pr)
if err != nil {
return err
}
// send pmt
s.pmt = &pmt
s.pmtPkt = packet
}
}
return
}
func (s *MpegTsStream) Feed(ts io.Reader) error {
var frame int64
var tsPktArr []MpegTsPacket
for {
packet, err := ReadTsPacket(ts)
if err == io.EOF {
// 文件结尾 把最后面的数据发出去
pesPkt, err := TsToPES(tsPktArr)
if err != nil {
return err
}
s.TsPesPktChan <- &MpegTsPesStream{
TsPkt: *s.firstTsPkt,
PesPkt: pesPkt,
}
return nil
}
if err != nil {
return err
}
pr := bytes.NewReader(packet.Payload)
err = s.readPAT(&packet, pr)
if err != nil {
return err
}
err = s.readPMT(&packet, pr)
if err != nil {
return err
}
// 在读取PMT中已经将所有的音视频PES的索引信息全部保存了起来
// 接着读取所有TS包里面的PID,找出PID==elementaryPID的TS包,就是音视频数据
for _, v := range s.pmt.Stream {
if v.ElementaryPID == packet.Header.Pid {
if packet.Header.PayloadUnitStartIndicator == 1 {
if frame != 0 {
pesPkt, err := TsToPES(tsPktArr)
if err != nil {
return err
}
s.TsPesPktChan <- &MpegTsPesStream{
TsPkt: *s.firstTsPkt,
PesPkt: pesPkt,
}
tsPktArr = nil
}
s.firstTsPkt = &packet
frame++
}
tsPktArr = append(tsPktArr, packet)
}
}
}
return nil
}

View File

@@ -1,520 +0,0 @@
#MPEGTS
----------
Name:苏荣
Data:2016/5/27 09:03:30
----------
## PSI(Program Specific Information) 节目特定信息
PSI 可以认为属于 6 个表:
1) 节目相关表(PAT)
2) TS 节目映射表(PMT)
3) 网络信息表(NIT)
4) 有条件访问表(CAT)
5) 传输流描述表
6) IPMP 控制信息表
##ES流(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流.
##PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流
##PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p).
##TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输).
##PES ES TS
视频压缩成H264码流,可以称之为ES流,将其每帧打包为PES流,然后分拆为多个188字节,称为TS流.
H264(ES) = PES1(一帧ES打包) + PES2(一帧ES打包) + PES3(一帧ES打包) + ...
PES1 = PES1 Header + PES1 Payload = PES1 Packet Start Code Prefix + Stream ID + PES1 Packet Length + Send PES1 Header(不确定大小) + PES1 Payload
PES1 Payload = TS1 Payload + TS2 Payload + TS3 Payload + ...
PES1 = TS1 + TS2 + TS3 + ....
PES1 = TS1(TS1 Header + PES1 Header + TS1 Payload) + TS2(有三种可能) + TS3(有三种可能) + ......
TS1(TS流第一个包) = TS1 Header + PES1 Header + TS1 Payload
TS2(TS流第二个包,第一种情况) = TS2 Header + 自适应字段 + TS2 Payload (出现概率 1%)
TS2(TS流第二个包,第二种情况) = TS2 Header + 自适应字段 (出现概率 0.1%)
TS2(TS流第二个包,第三种情况) = TS2 Header + TS2 Payload (出现概率 98.9%)
一段ES流 = N个PES(N帧)
同一个PES的TS的PID是相同的
##寻找第一个TS包
Header PID = 0x000 说明数据包是PAT表信息
第一个TS包 一般叫做 PAT Program Association Table,节目相关表)
TS流 : PID=005 + PID=002 + PID=000
一般来说第一个TS包一般在第一个位置,本例举出一个特殊情况(第一个TS包在第三)
在寻找第一个TS包时,不断读取TS包,直到找到pid=000的位置,并将读取过的TS包置入缓冲区
##寻找下一个TS包
第二个TS包 一般叫做PMT(Program Map Table,节目映射表)
##解析TS包
payload_unit_start_indicator : 该字段用来表示TS包的有效净荷有PES包或者PSI数据的情况.
当TS包带有PES包数据时(出现概率99.9%).不带PES包(出现概率0.1%).
1. 当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点:
a. 置为1,标识TS包的有效净荷以PES包的第一个字节开始.
b. 置为0,表示TS包的开始不是PES包.
2. 当TS包带有PSI数据时,payload_unit_start_indicator具有以下特点:
a. 置为1,表示TS包带有PSI部分的第一个字节,即第一个字节带有指针pointer_field.
b. 置为0,表示TS包不带有一个PSI部分的第一个字节,即在有效净荷中没有指针point_field.
c. 对于空包的包,payload_unit_start_indicator应该置为0
adaptionFieldControl:
01 -> 仅含有效负载(TS包第三种情况)
10 -> 仅含调整字段(TS包第二种情况)
11 -> 含有调整字段和有效负载(TS包第一种情况)
TS流,通过一个个的TS包来传送. TS包可以是传送PSI SI等各表的数据包,也可以是传送节目音视频数据(携带的PES包:音视频基本流包)的包TS携带 PSI SI等表的数据时,各个表以各表对应的Section语法格式做为传输单元存放到TS包中 以便传输;
TS包,有一个TS包的PID,系统就是根据这个PID来找对应的TS包对于包含音视频数据PES包的TS包,系统通过TS的PID找到对应TS数据包,提取其中的数据组合成节目的音视频对于携带PSI SI等数据的TS包,系统通过TS的PID找到对应TS数据包,提取各个PSI SI数据表格,用来指导系统因此其中部分PID用来固定传输某些数据内容.
有了TS的PID后, 如果TS包携带的是PSI SI等表格的Section数据时,有时还不能确定该PID的TS包中携带的数据是什么,SDT BAT ST 等表传送时,都用的是PID为0X0011的TS数据包,对于这种携带PSI SI Section单元的TS包,对应的数据(表的Section语法中)还有一个 TABLE_ID字段,用来可以确定是具体的什么表
因此PID+TableID就可以确定负载带了什么,是PES还是PSI.
----------
1. 第一个包:
包头 : 47 60 00 10
0x47 : syncByte
0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
0x000 : 0 0000 0000 0000, pid = 0,说明是第一个TS包(PAT表)
0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
负载 : 00 00 B0 0D 00 00 C1 00 00 00 01 E0
81 0C 8C BE 32 FF FF......FF
指针 : 00
table id : 00
固定值 : B (1011)
section_length : 0 0D(值:13)
transport_stream_id : 00 00
version number & current_next_indicator : C1
section_number : 00
last_section_number : 00
program_number : 00 01
program_map_PID : E0 81(因为program_number > 0)
CRC_32 : 0C 8C BE 32
if (program_number == 0)
{
network_PID
}else
{
program_map_PID
}
E0 81 = reserved3 + program_map_PID = | 1110 0000 | 1000 0001 |
program_map_PID = 0x81(说明PMT的pid为081)
----------
2. 第二个包
包头 : 47 60 81 10
0x47 : syncByte
0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
0x081 : 0 0000 1000 0001, pid = 0x081(说明是PMT表,因为前面的PAT表给出了)
0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
负载 : 00 02 B0 17 00 01 C1 00 00 E8 10 F0 00 1B E8 10
F0 00 03 E8 14 F0 00 66 74 A4 2D FF FF FF FF FF......FF
指针 : 00
table id : 02
固定值 : B
section_length : 0 17(值:23,表示到后面FF FF FF FF FF FF之前总共有23个字节)
program_number : 00 01
reserved2 & version_number & current_next_indicator : C1
section_number : 00
last_section_number : 00
PCR_PID : E8 10
program_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
第一流分析 : 1B E8 10 F0 00
stream_type : 1B 视频流(H264)(ITU-T H.264建议书| SO/IEC 14496-10 视频中定义的 AVC 视频流)
elementary_PID : E8 10 前3位为保留位取后13位 则PID=810 表示此PID的都是视频流
ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
第二流分析 : 03 E8 14 F0 00
stream_type : 03 音频流(MP3)
elementary_PID : E8 14 前3位为保留位取后13位 则PID=814 表示此PID的都是音频流
ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
CRC : 66 74 A4 2D
reserved4 + program_info_length = | 1111 0000 | 0000 0000 |
program_info_length = 0
stream_type : 03 表示流是音频流 MP3 格式 814 表示 pid=814 的TS包存储的是MP3格式的音频流.
stream_type : 01 表示流是视频流 h264格式 810 表示 pid=810 的TS包存储的是h264格式的视频流
----------
3. 第三个包
包头 : 47 48 14 10
0x47 : syncByte
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
0x10 : 0001 0000, adaptionFieldControl = 01
这里:
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 96 07 FF FD 85 00 33 22 22 11 22 11 11 11 11 11 11 24 82 41 00 90 40 00 00 00 00 00 40 00 ....... 70 34 5B CE 64 B7 D2 F5 4E 07 50 8E 11 1E 60 61 21 32 11 59
packetStartCodePrefix : 00 00 01
streamID : C0
pes_PacketLength : 01 88(值为392,占用392个字节,一帧数据长度,也可以置为0)
Sned PES HEADER : 占用不确定位 本例为:80 80 05 21 00 01 96 07
Sned PES HEADER 包括以下几个字段: 80 80 05 21 00 01 96 07(解析为二进制显示)
| 8 0 | 8 0 | 0 5 | 2 1 | 0 0 | 0 1 | 9 6 | 0 7 |
| 1000 0000| 1000 0000 | 0000 0101 | 0010 0001 | 0000 0000 | 0000 0001 | 1001 0110 | 0000 1110 |
(注意,下面的数值是用二进制表示,不特别声明,都是用16进制表示)
(0x80)
constTen : 10 固定
PES_scrambling_control : 00 PES加扰控制
PES_priority : 0 PES 包中该有效载荷的优先级
data_alignment_indicator : 0 数据定位指示符
copyright : 0 PES 包有效载荷的素材依靠版权所保护
original_or_copy : 0 PES 包有效载荷的内容是原始的
(0x80)
PTS_DTS_flags : 10 PES 包头中 PTS 字段存在
ESCR_flag : 0
ES_rate_flag : 0
DSM_trick_mode_flag : 0
additional_copy_info_flag : 0
PES_CRC_flag : 0
PES_extension_flag : 0
(0x05)
PES_header_data_length : 0000 0101(值为5)PES头数据长度,表示后面还有5个字节,之后就是一帧的数据
(0x4200032C)(十进制:1107297068)
PTS(presentation time stamp): 0010 0001 0000 0000 0000 0001 1001 0110 0
下面字段在本例中都没有:
ESCR(42) = ESCR_base(33) + ESCR_extension(9)
ES_rate(22)
DSM特技方式(8)
additional_copy_info(7)
previous_PES_packet_CRC(16)
PES_Extension(不确定)
因为 PTS_DTS_flags == 10,所以本例中只有PTS没有DTS.
注意 : 本TS包 包含PES头信息 说明开始下一帧
----------
4. 第四个包
包头 : 47 08 14 11
0x47 : syncByte
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
0x11 : 0001 0001, adaptionFieldControl = 01
这里:
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
----------
5. 第五个包
包头 : 47 08 14 32
0x47 : syncByte
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
0x32 : 0011 0010, adaptionFieldControl = 11
这里:
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
adaptionFieldControl = 11, 说明先有自适应字段,再有有效载荷(TS包第一种情况)
负载 : 99 00 FF FF FF ... FF 52 DE E6 B5 D0 76 CD CB B2 24 B3 92 AD 4E CD 19 D2 CC 82 D4 78 10 80 6C 0E 99 49 A4 59 C0
adaptation_field_length : 99(值为153,表示占用153个字节)
discontinuity_indicator & random_access_indicator &
elementary_stream_priority_indicator & PCR_flag &
OPCR_flag & splicing_point_flag &
transport_private_data_flag & adaptation_field_extension_flag : 00 剩下的所有字段都为0
(00 FF FF FF ... FF)这里都是调整字段,从52 DE E6 B5 D0(从00(FF之前,99之后) 开始算是第1个字节,跳到第153个字节)开始,就是真正的帧数据了
----------
6. 第六个包
包头 : 47 48 14 13
0x47 : syncByte
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
0x13 : 0001 0011, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
这里:
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 A6 E7 FF FD
packetStartCodePrefix : 00 00 01
streamID : C0
pes_PacketLength : 01 88(值为392,占用392个字节)
Sned PES HEADER : 占用不确定位
所以本包数据流ID 和 第二个包的流ID是一样的
注意 : 本TS包 又包含PES头信息 说明开始下一帧
----------
7. 第七个包
包头 : 47 48 10 30
0x47 : syncByte
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
0x810 : 0 1000 0001 0000, pid = 0x810(视频H264)
0x30 : 0011 0000, adaptionFieldControl = 11,说明含有调整字段和有效负载(TS包第一种情况)
这里:
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
adaptionFieldControl = 11, 说明含有调整字段和有效负载(TS包第一种情况)
负载 : 07 10 00 00 01 0F 7E 88 00 00 01 E0 00 00 80 C0 0A 31 00 01 96 07 11 00 01 7E 91 00 00 00 01 67 4D 40 1E 96 ...... D2 99 71 F3
adaptation_field_length : 07(值为7,表示占用153个字节)
discontinuity_indicator & random_access_indicator &
elementary_stream_priority_indicator & PCR_flag &
OPCR_flag & splicing_point_flag &
transport_private_data_flag & adaptation_field_extension_flag : 10
(10 00 00 01 0F 7E 88)调整字段
packetStartCodePrefix : 00 00 01
streamID : EO
pes_PacketLength : 00 00(值为0,占用0个字节,一帧数据长度,也可以置为0,此时需要自己去计算)
Sned PES HEADER : 占用不确定位
----------
8. 第八个包
包头 : 47 08 10 11
0x47 : syncByte
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
0x810 : 0 1000 0001 0000, pid = 0x810(视频H264)
0x11 : 0001 0001, adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
这里:
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
----------
总结这个八个包:
第一个TS包(PID:0X00) : 包含了PAT.
第二个TS包(PID:0X81) : 包含了PMT.
第三个TS包(PID:0x814) : 音频PES包头所有的TS包.
第四个TS包(PID:0x814) : 音频TS包.
第五个TS包(PID:0x814) : 音频TS包.
第六个TS包(PID:0x814) : 音频PES包头所有的TS包.
第七个TS包(PID:0x810) : 视频PES包头所有的TS包.
第八个TS包(PID:0x810) : 视频TS包.
----------
// Packet Header:
// PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的.如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,
// 那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video,Audio或其他业务信息).
// 分析一个Header:
// 二进制: 0100 0111 0000 0111 1110 0101 0001 0010
// 十六进制: 4 7 0 7 e 5 1 2
// syncByte = 0x47 就是0x47,这是DVB TS规定的同步字节,固定是0x47
// transportErrorIndicator = 0 表示当前包没有发生传输错误
// payloadUnitStartIndicator = 0 具体含义参考ISO13818-1标准文档
// transportPriority = 0 表示当前包是低优先级
// pid = 0x07e5(0 0111 1110 0101) Video PID
// transportScramblingControl = 00 表示节目没有加密
// adaptionFieldControl = 01 具体含义参考ISO13818-1标准文档
// continuityCounter = 0010 表示当前传送的相同类型的包是第3个
----------
// 分析一段TS流:(PAT)
// Packet Header : 0x47 0x40 0x00 0x10
// Packet Data : 00 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff
// Header PID = 0x0000 说明数据包是PAT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
// 所以,Packet Data就应该是 : 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff
//
// 00 | b0 11 | 00 01 | c1 | 00 | 00 | 00 00 | e0 1f | 00 01 e1 00 |
//
// table_id = 0000 0000
// section_syntax_indicator = 1
// zero = 0
// reserved1 = 11
// sectionLength = 0000 0001 0001
// transportStreamID = 0000 0000 0000 0001
// reserved2 = 11
// versionNumber = 0000 0
// currentNextIndicator 1
// sectionNumber = 0000 0000
// lastSectionNumber = 0000 0000
// programNumber = 0000 0000 0000 0000
// reserved3 = 111
// networkPID = 0 0000 0001 1111
// crc32
----------
// 分析一段TS流:(PMT)
// Packet Header : 0x47 0x43 0xe8 0x12
// Packet Data : 00 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff
// Header PID = 0x03e8 说明数据包是PMT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
// 所以,Packet Data就应该是 : 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff
// 1 2 3 4 5 6 7 8 9 10 11 12
// 02 | b0 12 | 00 01 | c1 | 00 | 00 | e3 e9 | f0 00 | 1b | e3 e9 | f0 00 | f0 af b4 4f |
//
// 1:
// table_id = 0000 0010
// 2:
// section_syntax_indicator = 1
// zero = 0
// reserved1 = 11
// section_length = 0000 0001 0010
// 3:
// program_number = 0000 0000 0000 0001
// 4:
// reserved2 = 11
// version_number = 00 000
// current_next_indicator = 1
// 5:
// section_number = 0000 0000
// 6:
// last_section_number = 0000 0000
// 7:
// reserved3 = 111
// PCR_PID = 0 0011 1110 1001
// 8:
// reserved4 = 1111
// program_info_length = 0000 0000 0000
// 9:
// stream_type = 0001 1011
// 10:
// reserved5 = 111
// elementary_PID = 0 0011 1110 1001
// 11:
// reserved6 = 1111
// ES_info_length = 0000 0000 0000
// 12:
// crc
----------
##TS流解码过程
1. 获取TS中的PAT
2. 获取TS中的PMT
3. 根据PMT可以知道当前网络中传输的视频音频类型H264,相应的PID,PCR的PID等信息.
4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等.
5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包. 因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包.
6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES.
7. 直接将 被被拔掉 PES包头的ES包送给decoder就可以进行解码.解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步.
8. I,B,B,P 信息是在ES中的.
----------
1. 首先找到PID为0x00的TS包,找到里面的节目映射表PMT)PID,因为可能有几个节目信息.所以可能有几个PMT_PID,以一个为例
2. 接着查找该PMT_PID的TS包,通常就紧接着.在该PMT包中找音频和视频的PID.以视频为例.
3. 开始提取一帧ES数据
3.1 查找视频PID的TS包
3.2 找PES包头,方法:TS包头第2个字节的高6位有效载荷单元起始指示符为1的TS包,跳过自适应字段,找到PES包头,提取时间戳,再跳至ES数据,这就是一帧ES数据的开始部分.
3.3 查找有效载荷单元起始指示符为0的TS包.跳过TS包头,跳过自适应字段,提取后面的ES数据
3.4 同3.3接着查找
3.5 当碰到有效载荷单元起始指示符又变为1的视频TS包,就知道这是下一帧的开始了,将前面的所有ES数据组合成一帧数据.开始下一轮组帧.
----------
##参考文档:
1. [TS流](http://blog.csdn.net/cabbage2008/article/category/5885203)
1. [TS各个表 与 SECTION 的解析 CAS原理 ](http://blog.sina.com.cn/s/blog_6b94d5680101r5l6.html)

View File

@@ -1,60 +0,0 @@
package mpegts
// http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c
var Crc32_Table = []uint32{
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
}
func GetCRC32(data []byte) (crc uint32) {
crc = 0xffffffff
for _, v := range data {
crc = (crc << 8) ^ Crc32_Table[((crc>>24)^uint32(v))&0xff]
}
return
}

View File

@@ -1,229 +0,0 @@
package mpegts
import (
"bytes"
"errors"
"fmt"
"github.com/langhuihui/monibuca/monica/util"
"io"
)
// ios13818-1-CN.pdf 43(57)/166
//
// PAT
//
var DefaultPATPacket = []byte{
// TS Header
0x47, 0x40, 0x00, 0x10,
// Pointer Field
0x00,
// PSI
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
// PAT
0x00, 0x01, 0xe1, 0x00,
// CRC
0xe8, 0xf9, 0x5e, 0x7d,
// Stuffing 167 bytes
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}
// TS Header :
// SyncByte = 0x47
// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0),
// Pid = 0,
// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000),
// PSI :
// TableID = 0x00,
// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11),
// SectionLength = 13(0x00d)
// TransportStreamID = 0x0001
// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0),
// SectionNumber = 0x00
// LastSectionNumber = 0x00
// PAT :
// ProgramNumber = 0x0001
// Reserved3 = 15(B:1110), ProgramMapPID = 4097(0x1001)
// PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来以后会使用到这些数据
type MpegTsPATProgram struct {
ProgramNumber uint16 // 16 bit 节目号
Reserved3 byte // 3 bits 保留位
NetworkPID uint16 // 13 bits 网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID
ProgramMapPID uint16 // 13 bit 节目映射表的PID,节目号大于0时对应的PID.每个节目对应一个
}
// Program Association Table (节目关联表)
// 节目号为0x0000时,表示这是NIT,PID=0x001f,即3.
// 节目号为0x0001时,表示这是PMT,PID=0x100,即256
type MpegTsPAT struct {
// PSI
TableID byte // 8 bits 0x00->PAT,0x02->PMT
SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1
Zero byte // 1 bit 0
Reserved1 byte // 2 bits 保留位
SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD)
TransportStreamID uint16 // 16 bits 该字段充当标签,标识网络内此传输流有别于任何其他多路复用流.其值由用户规定
Reserved2 byte // 2 bits 保留位
VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号
CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效,0则要等待下一个表
SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段
LastSectionNumber byte // 8 bits 最后一个分段的号码
// N Loop
Program []MpegTsPATProgram // PAT表里面的所有频道索引信息
Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值
}
func ReadPAT(r io.Reader) (pat MpegTsPAT, err error) {
lr, psi, err := ReadPSI(r, PSI_TYPE_PAT)
if err != nil {
return
}
pat = psi.Pat
// N Loop
// 一直循环去读4个字节,用lr的原因是确保不会读过头了.
for lr.N > 0 {
// 获取每一个频道的节目信息,保存起来
programs := MpegTsPATProgram{}
programs.ProgramNumber, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
// 如果programNumber为0,则是NetworkPID,否则是ProgramMapPID(13)
if programs.ProgramNumber == 0 {
programs.NetworkPID, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
programs.NetworkPID = programs.NetworkPID & 0x1fff
} else {
programs.ProgramMapPID, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
programs.ProgramMapPID = programs.ProgramMapPID & 0x1fff
}
pat.Program = append(pat.Program, programs)
}
if cr, ok := r.(*util.Crc32Reader); ok {
err = cr.ReadCrc32UIntAndCheck()
if err != nil {
return
}
}
return
}
func WritePAT(w io.Writer, pat MpegTsPAT) (err error) {
bw := &bytes.Buffer{}
// 将pat(所有的节目索引信息)写入到缓冲区中
for _, pats := range pat.Program {
if err = util.WriteUint16ToByte(bw, pats.ProgramNumber, true); err != nil {
return
}
if pats.ProgramNumber == 0 {
if err = util.WriteUint16ToByte(bw, pats.NetworkPID&0x1fff|7<<13, true); err != nil {
return
}
} else {
// | 0001 1111 | 1111 1111 |
// 7 << 13 -> 1110 0000 0000 0000
if err = util.WriteUint16ToByte(bw, pats.ProgramMapPID&0x1fff|7<<13, true); err != nil {
return
}
}
}
if pat.SectionLength == 0 {
pat.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes()))
}
psi := MpegTsPSI{}
psi.Pat = pat
if err = WritePSI(w, PSI_TYPE_PAT, psi, bw.Bytes()); err != nil {
return
}
return
}
func WritePATPacket(w io.Writer, tsHeader []byte, pat MpegTsPAT) (err error) {
if pat.TableID != TABLE_PAS {
err = errors.New("PAT table ID error")
return
}
// 将所有要写的数据(PAT),全部放入到buffer中去.
// buffer 里面已经写好了整个pat表(PointerField+PSI+PAT+CRC)
bw := &bytes.Buffer{}
if err = WritePAT(bw, pat); err != nil {
return
}
// TODO:如果Pat.Program里面包含的信息很大,大于188?
stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len())
// PATPacket = TsHeader + PAT + Stuffing Bytes
var PATPacket []byte
PATPacket = append(PATPacket, tsHeader...)
PATPacket = append(PATPacket, bw.Bytes()...)
PATPacket = append(PATPacket, stuffingBytes...)
fmt.Println("-------------------------")
fmt.Println("Write PAT :", PATPacket)
fmt.Println("-------------------------")
// 写PAT负载
if _, err = w.Write(PATPacket); err != nil {
return
}
return
}
func WriteDefaultPATPacket(w io.Writer) (err error) {
_, err = w.Write(DefaultPATPacket)
if err != nil {
return
}
return
}

View File

@@ -1,753 +0,0 @@
package mpegts
import (
"bytes"
"errors"
"fmt"
"github.com/langhuihui/monibuca/monica/avformat"
"github.com/langhuihui/monibuca/monica/util"
"io"
"io/ioutil"
)
// ios13818-1-CN.pdf 45/166
//
// PES
//
// 每个传输流和节目流在逻辑上都是由 PES 包构造的
type MpegTsPesStream struct {
TsPkt MpegTsPacket
PesPkt MpegTsPESPacket
}
// PES--Packetized Elementary Streams (分组的ES),ES形成的分组称为PES分组,是用来传递ES的一种数据结构
// 1110 xxxx 为视频流(0xE0)
// 110x xxxx 为音频流(0xC0)
type MpegTsPESPacket struct {
Header MpegTsPESHeader
Payload []byte
}
type MpegTsPESHeader struct {
PacketStartCodePrefix uint32 // 24 bits 同跟随它的 stream_id 一起组成标识包起始端的包起始码.packet_start_code_prefix 为比特串"0000 0000 0000 0000 0000 0001"(0x000001)
StreamID byte // 8 bits stream_id 指示基本流的类型和编号,如 stream_id 表 2-22 所定义的.传输流中,stream_id 可以设置为准确描述基本流类型的任何有效值,如表 2-22 所规定的.传输流中,基本流类型在 2.4.4 中所指示的节目特定信息中指定
PesPacketLength uint16 // 16 bits 指示 PES 包中跟随该字段最后字节的字节数.0->指示 PES 包长度既未指示也未限定并且仅在这样的 PES 包中才被允许,该 PES 包的有效载荷由来自传输流包中所包含的视频基本流的字节组成
MpegTsOptionalPESHeader
PayloadLength uint64 // 这个不是标准文档里面的字段,是自己添加的,方便计算
}
// 可选的PES Header = MpegTsOptionalPESHeader + stuffing bytes(0xFF) m * 8
type MpegTsOptionalPESHeader struct {
ConstTen byte // 2 bits 常量10
PesScramblingControl byte // 2 bit 指示 PES 包有效载荷的加扰方式.当加扰在 PES 等级上实施时, PES 包头,其中包括任选字段只要存在,应不加扰(见表 2-23)
PesPriority byte // 1 bit 指示在此 PES 包中该有效载荷的优先级.1->指示该 PES 包有效载荷比具有此字段置于"0"的其他 PES 包有效载荷有更高的有效载荷优先级.多路复用器能够使用该PES_priority 比特最佳化基本流内的数据
DataAlignmentIndicator byte // 1 bit 1->指示 PES 包头之后紧随 2.6.10 中data_stream_alignment_descriptor 字段中指示的视频句法单元或音频同步字,只要该描述符字段存在.若置于值"1"并且该描述符不存在,则要求表 2-53,表 2-54 或表 2-55 的 alignment_type"01"中所指示的那种校准.0->不能确定任何此类校准是否发生
Copyright byte // 1 bit 1->指示相关 PES 包有效载荷的素材依靠版权所保护.0->不能确定该素材是否依靠版权所保护
OriginalOrCopy byte // 1 bit 1->指示相关 PES 包有效载荷的内容是原始的.0->指示相关 PES 包有效载荷的内容是复制的
PtsDtsFlags byte // 2 bits 10->PES 包头中 PTS 字段存在. 11->PES 包头中 PTS 字段和 DTS 字段均存在. 00->PES 包头中既无任何 PTS 字段也无任何 DTS 字段存在. 01->禁用
EscrFlag byte // 1 bit 1->指示 PES 包头中 ESCR 基准字段和 ESCR 扩展字段均存在.0->指示无任何 ESCR 字段存在
EsRateFlag byte // 1 bit 1->指示 PES 包头中 ES_rate 字段存在.0->指示无任何 ES_rate 字段存在
DsmTrickModeFlag byte // 1 bit 1->指示 8 比特特技方式字段存在.0->指示此字段不存在
AdditionalCopyInfoFlag byte // 1 bit 1->指示 additional_copy_info 存在.0->时指示此字段不存在
PesCRCFlag byte // 1 bit 1->指示 PES 包中 CRC 字段存在.0->指示此字段不存在
PesExtensionFlag byte // 1 bit 1->时指示 PES 包头中扩展字段存在.0->指示此字段不存在
PesHeaderDataLength byte // 8 bits 指示在此 PES包头中包含的由任选字段和任意填充字节所占据的字节总数.任选字段的存在由前导 PES_header_data_length 字段的字节来指定
// Optional Field
Pts uint64 // 33 bits 指示时间与解码时间的关系如下: PTS 为三个独立字段编码的 33 比特数.它指示基本流 n 的显示单元 k 在系统目标解码器中的显示时间 tpn(k).PTS 值以系统时钟频率除以 300(产生 90 kHz)的周期为单位指定.显示时间依照以下公式 2-11 从 PTS 中推出.有关编码显示时间标记频率上的限制参阅 2.7.4
Dts uint64 // 33 bits 指示基本流 n 的存取单元 j 在系统目标解码器中的解码时间 tdn(j). DTS 的值以系统时钟频率除以 300生成90 kHz)的周期为单位指定.依照以下公式 2-12 从 DTS 中推出解码时间
EscrBase uint64 // 33 bits 其值由 ESCR_base(i) 给出,如公式 2-14 中给出的
EscrExtension uint16 // 9 bits 其值由 ESCR_ext(i) 给出,如公式 2-15 中给出的. ESCR 字段指示包含 ESCR_base 最后比特的字节到达 PES流的 PES-STD 输入端的预期时间(参阅 2.5.2.4)
EsRate uint32 // 22 bits 在PES 流情况中,指定系统目标解码器接收 PES 包字节的速率.ES_rate 在包括它的 PES 包以及相同 PES 流的后续 PES 包中持续有效直至遇到新的 ES_rate 字段时为止.ES 速率值以 50 字节/秒为度量单位.0 值禁用
TrickModeControl byte // 3 bits 指示适用于相关视频流的特技方式.在其他类型基本流的情况中,此字段以及后随 5 比特所规定的那些含义未确定.对于 trick_mode 状态的定义,参阅 2.4.2.3 的特技方式段落
TrickModeValue byte // 5 bits
AdditionalCopyInfo byte // 7 bits 包含与版权信息有关的专用数据
PreviousPESPacketCRC uint16 // 16 bits 包含产生解码器中 16 寄存器零输出的 CRC 值, 类似于附件 A 中定义的解码器. 但在处理先前的 PES 包数据字节之后, PES 包头除外,采用多项式
// PES Extension
PesPrivateDataFlag byte // 1 bit 1->指示该 PES 包头包含专用数据. 0->指示 PES 包头中不存在专用数据
PackHeaderFieldFlag byte // 1 bit 1->指示 ISO/IEC 11172-1 包头或节目流包头在此 PES包头中存储.若此字段处于节目流中包含的 PES 包中,则此字段应设置为"0.传输流中, 0->指示该 PES 头中无任何包头存在
ProgramPacketSequenceCounterFlag byte // 1 bit 1->指示 program_packet_sequence_counter, MPEG1_MPEG2_identifier 以及 original_stuff_length 字段在 PES 包中存在.0->它指示这些字段在 PES 头中不存在
PSTDBufferFlag byte // 1 bit 1->指示 P-STD_buffer_scale 和 P-STD_buffer_size 在 PES包头中存在.0->指示这些字段在 PES 头中不存在
Reserved byte // 3 bits
PesExtensionFlag2 byte // 1 bits 1->指示 PES_extension_field_length 字段及相关的字段存在.0->指示 PES_extension_field_length 字段以及任何相关的字段均不存在.
// Optional Field
PesPrivateData [16]byte // 128 bits 此数据,同前后字段数据结合,应不能仿真packet_start_code_prefix (0x000001)
PackHeaderField byte // 8 bits 指示 pack_header_field() 的长度,以字节为单位
ProgramPacketSequenceCounter byte // 7 bits
Mpeg1Mpeg2Identifier byte // 1 bit 1->指示此 PES 包承载来自 ISO/IEC 11172-1 流的信息.0->指示此 PES 包承载来自节目流的信息
OriginalStuffLength byte // 6 bits 在原始 ITU-T H.222.0 建议书| ISO/IEC 13818-1 PES 包头或在原始 ISO/IEC 11172-1 包头中所使用的填充字节数
PSTDBufferScale byte // 1bit 它的含义仅当节目流中包含此 PES 包时才规定.它指示所使用的标度因子用于解释后续的 P-STD_buffer_size 字段.若前导 stream_id 指示音频流,则P-STD 缓冲器标度字段必为"0"值.若前导 stream_id 指示视频流,则 P-STD_buffer_scale 字段必为"1"值.对于所有其他流类型,该值可为"1"或为"0"
PSTDBufferSize uint16 // 13 bits 其含义仅当节目流中包含此 PES包时才规定.它规定在 P-STD 中,输入缓冲器 BSn 的尺寸.若 STD_buffer_scale 为 "0"值,则 P-STD_buffer_size以 128 字节为单位度量该缓冲器尺寸.若 P-STD_buffer_scale 为"1",则 P-STD_buffer_size 以 1024 字节为单位度量该缓冲器尺寸
PesExtensionFieldLength byte // 7 bits 指示 PES 扩展字段中跟随此长度字段的直至并包括任何保留字节为止的数据长度,以字节为度量单位
StreamIDExtensionFlag byte // 1 bits
//pesExtensionField []byte // PES_extension_field_length bits
//packField []byte // pack_field_length bits
}
// pts_dts_Flags == "10" -> PTS
// 0010 4
// PTS[32...30] 3
// marker_bit 1
// PTS[29...15] 15
// marker_bit 1
// PTS[14...0] 15
// marker_bit 1
// pts_dts_Flags == "11" -> PTS + DTS
type MpegtsPESFrame struct {
Pid uint16
IsKeyFrame bool
ContinuityCounter byte
ProgramClockReferenceBase uint64
}
func ReadPESHeader(r io.Reader) (header MpegTsPESHeader, err error) {
var flags uint8
var length uint
// packetStartCodePrefix(24) (0x000001)
header.PacketStartCodePrefix, err = util.ReadByteToUint24(r, true)
if err != nil {
return
}
if header.PacketStartCodePrefix != 0x0000001 {
err = errors.New("read PacketStartCodePrefix is not 0x0000001")
return
}
// streamID(8)
header.StreamID, err = util.ReadByteToUint8(r)
if err != nil {
return
}
// pes_PacketLength(16)
header.PesPacketLength, err = util.ReadByteToUint16(r, true)
if err != nil {
return
}
length = uint(header.PesPacketLength)
// PES包长度可能为0,这个时候,需要自己去算
// 0 <= len <= 65535
// 如果当length为0,那么先设置为最大值,然后用LimitedReade去读,如果读到最后面剩下的字节数小于65536,才是正确的包大小.
// 一个包一般情况下不可能会读1<<31个字节.
if length == 0 {
length = 1 << 31
}
// lrPacket 和 lrHeader 位置指针是在同一位置的
lrPacket := &io.LimitedReader{R: r, N: int64(length)}
lrHeader := lrPacket
// constTen(2)
// pes_ScramblingControl(2)
// pes_Priority(1)
// dataAlignmentIndicator(1)
// copyright(1)
// originalOrCopy(1)
flags, err = util.ReadByteToUint8(lrHeader)
if err != nil {
return
}
header.ConstTen = flags & 0xc0
header.PesScramblingControl = flags & 0x30
header.PesPriority = flags & 0x08
header.DataAlignmentIndicator = flags & 0x04
header.Copyright = flags & 0x02
header.OriginalOrCopy = flags & 0x01
// pts_dts_Flags(2)
// escr_Flag(1)
// es_RateFlag(1)
// dsm_TrickModeFlag(1)
// additionalCopyInfoFlag(1)
// pes_CRCFlag(1)
// pes_ExtensionFlag(1)
flags, err = util.ReadByteToUint8(lrHeader)
if err != nil {
return
}
header.PtsDtsFlags = flags & 0xc0
header.EscrFlag = flags & 0x20
header.EsRateFlag = flags & 0x10
header.DsmTrickModeFlag = flags & 0x08
header.AdditionalCopyInfoFlag = flags & 0x04
header.PesCRCFlag = flags & 0x02
header.PesExtensionFlag = flags & 0x01
// pes_HeaderDataLength(8)
header.PesHeaderDataLength, err = util.ReadByteToUint8(lrHeader)
if err != nil {
return
}
length = uint(header.PesHeaderDataLength)
lrHeader = &io.LimitedReader{R: lrHeader, N: int64(length)}
// 00 -> PES 包头中既无任何PTS 字段也无任何DTS 字段存在
// 10 -> PES 包头中PTS 字段存在
// 11 -> PES 包头中PTS 字段和DTS 字段均存在
// 01 -> 禁用
// PTS(33)
if flags&0x80 != 0 {
var pts uint64
pts, err = util.ReadByteToUint40(lrHeader, true)
if err != nil {
return
}
header.Pts = util.GetPtsDts(pts)
}
// DTS(33)
if flags&0x80 != 0 && flags&0x40 != 0 {
var dts uint64
dts, err = util.ReadByteToUint40(lrHeader, true)
if err != nil {
return
}
header.Dts = util.GetPtsDts(dts)
}
// reserved(2) + escr_Base1(3) + marker_bit(1) +
// escr_Base2(15) + marker_bit(1) + escr_Base23(15) +
// marker_bit(1) + escr_Extension(9) + marker_bit(1)
if header.EscrFlag != 0 {
_, err = util.ReadByteToUint48(lrHeader, true)
if err != nil {
return
}
//s.pes.escr_Base = escrBaseEx & 0x3fffffffe00
//s.pes.escr_Extension = uint16(escrBaseEx & 0x1ff)
}
// es_Rate(22)
if header.EsRateFlag != 0 {
header.EsRate, err = util.ReadByteToUint24(lrHeader, true)
if err != nil {
return
}
}
// 不知道为什么这里不用
/*
// trickModeControl(3) + trickModeValue(5)
if s.pes.dsm_TrickModeFlag != 0 {
trickMcMv, err := util.ReadByteToUint8(lrHeader)
if err != nil {
return err
}
s.pes.trickModeControl = trickMcMv & 0xe0
s.pes.trickModeValue = trickMcMv & 0x1f
}
*/
// marker_bit(1) + additionalCopyInfo(7)
if header.AdditionalCopyInfoFlag != 0 {
header.AdditionalCopyInfo, err = util.ReadByteToUint8(lrHeader)
if err != nil {
return
}
header.AdditionalCopyInfo = header.AdditionalCopyInfo & 0x7f
}
// previous_PES_Packet_CRC(16)
if header.PesCRCFlag != 0 {
header.PreviousPESPacketCRC, err = util.ReadByteToUint16(lrHeader, true)
if err != nil {
return
}
}
// pes_PrivateDataFlag(1) + packHeaderFieldFlag(1) + programPacketSequenceCounterFlag(1) +
// p_STD_BufferFlag(1) + reserved(3) + pes_ExtensionFlag2(1)
if header.PesExtensionFlag != 0 {
var flags uint8
flags, err = util.ReadByteToUint8(lrHeader)
if err != nil {
return
}
header.PesPrivateDataFlag = flags & 0x80
header.PackHeaderFieldFlag = flags & 0x40
header.ProgramPacketSequenceCounterFlag = flags & 0x20
header.PSTDBufferFlag = flags & 0x10
header.PesExtensionFlag2 = flags & 0x01
// TODO:下面所有的标志位,可能获取到的数据,都简单的读取后,丢弃,如果日后需要,在这里处理
// pes_PrivateData(128)
if header.PesPrivateDataFlag != 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(16)); err != nil {
return
}
}
// packFieldLength(8)
if header.PackHeaderFieldFlag != 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(1)); err != nil {
return
}
}
// marker_bit(1) + programPacketSequenceCounter(7) + marker_bit(1) +
// mpeg1_mpeg2_Identifier(1) + originalStuffLength(6)
if header.ProgramPacketSequenceCounterFlag != 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil {
return
}
}
// 01 + p_STD_bufferScale(1) + p_STD_bufferSize(13)
if header.PSTDBufferFlag != 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil {
return
}
}
// marker_bit(1) + pes_Extension_Field_Length(7) +
// streamIDExtensionFlag(1)
if header.PesExtensionFlag != 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil {
return
}
}
}
// 把剩下的头的数据消耗掉
if lrHeader.N > 0 {
if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(lrHeader.N)); err != nil {
return
}
}
// 2的16次方,16个字节
if lrPacket.N < 65536 {
// 这里得到的其实是负载长度,因为已经偏移过了Header部分.
//header.pes_PacketLength = uint16(lrPacket.N)
header.PayloadLength = uint64(lrPacket.N)
}
return
}
func WritePESHeader(w io.Writer, header MpegTsPESHeader) (written int, err error) {
if header.PacketStartCodePrefix != 0x0000001 {
err = errors.New("write PacketStartCodePrefix is not 0x0000001")
return
}
// packetStartCodePrefix(24) (0x000001)
if err = util.WriteUint24ToByte(w, header.PacketStartCodePrefix, true); err != nil {
return
}
written += 3
// streamID(8)
if err = util.WriteUint8ToByte(w, header.StreamID); err != nil {
return
}
written += 1
// pes_PacketLength(16)
// PES包长度可能为0,这个时候,需要自己去算
// 0 <= len <= 65535
if err = util.WriteUint16ToByte(w, header.PesPacketLength, true); err != nil {
return
}
//fmt.Println("Length :", payloadLength)
//fmt.Println("PES Packet Length :", header.pes_PacketLength)
written += 2
// constTen(2)
// pes_ScramblingControl(2)
// pes_Priority(1)
// dataAlignmentIndicator(1)
// copyright(1)
// originalOrCopy(1)
// 1000 0001
if header.ConstTen != 0x80 {
err = errors.New("pes header ConstTen != 0x80")
return
}
flags := header.ConstTen | header.PesScramblingControl | header.PesPriority | header.DataAlignmentIndicator | header.Copyright | header.OriginalOrCopy
if err = util.WriteUint8ToByte(w, flags); err != nil {
return
}
written += 1
// pts_dts_Flags(2)
// escr_Flag(1)
// es_RateFlag(1)
// dsm_TrickModeFlag(1)
// additionalCopyInfoFlag(1)
// pes_CRCFlag(1)
// pes_ExtensionFlag(1)
sevenFlags := header.PtsDtsFlags | header.EscrFlag | header.EsRateFlag | header.DsmTrickModeFlag | header.AdditionalCopyInfoFlag | header.PesCRCFlag | header.PesExtensionFlag
if err = util.WriteUint8ToByte(w, sevenFlags); err != nil {
return
}
written += 1
// pes_HeaderDataLength(8)
if err = util.WriteUint8ToByte(w, header.PesHeaderDataLength); err != nil {
return
}
written += 1
// PtsDtsFlags == 192(11), 128(10), 64(01)禁用, 0(00)
if header.PtsDtsFlags&0x80 != 0 {
// PTS和DTS都存在(11),否则只有PTS(10)
if header.PtsDtsFlags&0x80 != 0 && header.PtsDtsFlags&0x40 != 0 {
// 11:PTS和DTS
// PTS(33) + 4 + 3
pts := util.PutPtsDts(header.Pts) | 3<<36
if err = util.WriteUint40ToByte(w, pts, true); err != nil {
return
}
written += 5
// DTS(33) + 4 + 3
dts := util.PutPtsDts(header.Dts) | 1<<36
if err = util.WriteUint40ToByte(w, dts, true); err != nil {
return
}
written += 5
} else {
// 10:只有PTS
// PTS(33) + 4 + 3
pts := util.PutPtsDts(header.Pts) | 2<<36
if err = util.WriteUint40ToByte(w, pts, true); err != nil {
return
}
written += 5
}
}
return
}
func WritePESPacket(w io.Writer, frame *MpegtsPESFrame, packet MpegTsPESPacket) (err error) {
var tsPkts []byte
if tsPkts, err = PESToTs(frame, packet); err != nil {
return
}
// bw.Bytes == PES Packet
if _, err = w.Write(tsPkts); err != nil {
return
}
return
}
func IowWritePESPacket(w io.Writer, tsHeader MpegTsHeader, packet MpegTsPESPacket) (err error) {
if packet.Header.PacketStartCodePrefix != 0x000001 {
return errors.New("packetStartCodePrefix != 0x000001")
}
bw := &bytes.Buffer{}
// TODO:如果头长度大于65536,字段会为0,是否要改?
_, err = WritePESHeader(bw, packet.Header)
if err != nil {
return
}
PESPacket := &util.IOVec{}
PESPacket.Append(bw.Bytes()) // header
PESPacket.Append(packet.Payload) // packet
// 用IOVecWriter来写PES包,IOVecWriter实现了Write方法.
// 因为通常在将一帧PES封装成TS包(188字节)的时候,一般情况下一帧PES字节数会大于188,并且分多次封装.
// 例如这一帧PES字节数为189,那么在封装第二个TS包的时候就只会封装1字节,会导致多次写操作,降低性能.
// 因此将所有的字节数,都写进缓冲中去,然后用系统调用syscall来写入.
iow := util.NewIOVecWriter(w)
var isKeyFrame bool
var headerLength int
isKeyFrame = CheckPESPacketIsKeyFrame(packet)
// 写一帧PES
// 如果是I帧,会有pcr,所以会有调整字段AF.
// 如果当前包字节不满188字节,会需要填充0xff,所以会有调整字段AF.
for i := 0; PESPacket.Length > 0; i++ {
header := MpegTsHeader{
SyncByte: 0x47,
Pid: tsHeader.Pid,
AdaptionFieldControl: 1,
ContinuityCounter: byte(i % 15),
}
// 每一帧开头
if i == 0 {
header.PayloadUnitStartIndicator = 1
}
// I帧
if isKeyFrame {
header.AdaptionFieldControl = 0x03
header.AdaptationFieldLength = 7
header.PCRFlag = 1
header.RandomAccessIndicator = tsHeader.RandomAccessIndicator
header.ProgramClockReferenceBase = tsHeader.ProgramClockReferenceBase
header.ProgramClockReferenceExtension = tsHeader.ProgramClockReferenceExtension
isKeyFrame = false
}
// 这个包大小,会在每一次PESPacket.WriteTo中慢慢减少.
packetLength := PESPacket.Length
// 包不满188字节
if packetLength < TS_PACKET_SIZE-4 {
if header.AdaptionFieldControl >= 2 {
header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength - 7)
} else {
header.AdaptionFieldControl = 0x03
header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength)
}
headerLength, err = WriteTsHeader(iow, header)
if err != nil {
return
}
stuffingLength := int(header.AdaptationFieldLength - 1)
if _, err = iow.Write(util.GetFillBytes(0xff, stuffingLength)); err != nil {
return
}
headerLength += stuffingLength
} else {
headerLength, err = WriteTsHeader(iow, header)
if err != nil {
return
}
}
/*
if headerLength, err = writeTsHeader(iow, header, packetLength); err != nil {
return
}
*/
payloadLength := 188 - headerLength
// 写PES负载
if _, err = PESPacket.WriteTo(iow, payloadLength); err != nil {
return
}
}
iow.Flush()
return
}
func CheckPESPacketIsKeyFrame(packet MpegTsPESPacket) bool {
nalus := bytes.SplitN(packet.Payload, avformat.NALU_Delimiter1, -1)
for _, v := range nalus {
if v[0]&0x1f == avformat.NALU_IDR_Picture {
return true
}
}
return false
}
func TsToPES(tsPkts []MpegTsPacket) (pesPkt MpegTsPESPacket, err error) {
var index int
for i := 0; i < len(tsPkts); i++ {
if tsPkts[i].Header.SyncByte != 0x47 {
err = errors.New("mpegts header sync error!")
return
}
if tsPkts[i].Header.PayloadUnitStartIndicator == 1 {
index++
// 一个PES包里面只可能包含一个PayloadUnitStartIndicator=1的TS包.
if index >= 2 {
err = errors.New("TsToPES error PayloadUnitStartIndicator >= 2")
return
}
r := bytes.NewReader(tsPkts[i].Payload)
lr := &io.LimitedReader{R: r, N: int64(len(tsPkts[i].Payload))}
// TS Packet PES Header Start Index
hBegin := lr.N
// PES Header
pesPkt.Header, err = ReadPESHeader(lr)
if err != nil {
return
}
// TS Packet PES Header End Index
hEnd := lr.N
pesHeaderLength := hBegin - hEnd
if pesHeaderLength > 0 && pesHeaderLength <= hBegin {
pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload[pesHeaderLength:]...)
}
}
if tsPkts[i].Header.PayloadUnitStartIndicator == 0 {
// MpegTsPacket Header 已经包含了自适应字段在里面,所以MpegTsPacket Payload直接就是PES Pyaload
pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload...)
}
}
return
}
func PESToTs(frame *MpegtsPESFrame, packet MpegTsPESPacket) (tsPkts []byte, err error) {
if packet.Header.PacketStartCodePrefix != 0x000001 {
err = errors.New("packetStartCodePrefix != 0x000001")
return
}
bwPESPkt := &bytes.Buffer{}
_, err = WritePESHeader(bwPESPkt, packet.Header)
if err != nil {
return
}
if _, err = bwPESPkt.Write(packet.Payload); err != nil {
return
}
var tsHeaderLength int
for i := 0; bwPESPkt.Len() > 0; i++ {
bwTsHeader := &bytes.Buffer{}
tsHeader := MpegTsHeader{
SyncByte: 0x47,
TransportErrorIndicator: 0,
PayloadUnitStartIndicator: 0,
TransportPriority: 0,
Pid: frame.Pid,
TransportScramblingControl: 0,
AdaptionFieldControl: 1,
ContinuityCounter: frame.ContinuityCounter,
}
frame.ContinuityCounter++
frame.ContinuityCounter = frame.ContinuityCounter % 16
// 每一帧的开头,当含有pcr的时候,包含调整字段
if i == 0 {
tsHeader.PayloadUnitStartIndicator = 1
// 当PCRFlag为1的时候,包含调整字段
if frame.IsKeyFrame {
tsHeader.AdaptionFieldControl = 0x03
tsHeader.AdaptationFieldLength = 7
tsHeader.PCRFlag = 1
tsHeader.RandomAccessIndicator = 1
tsHeader.ProgramClockReferenceBase = frame.ProgramClockReferenceBase
}
}
pesPktLength := bwPESPkt.Len()
// 每一帧的结尾,当不满足188个字节的时候,包含调整字段
if pesPktLength < TS_PACKET_SIZE-4 {
var tsStuffingLength uint8
tsHeader.AdaptionFieldControl = 0x03
tsHeader.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - pesPktLength)
// TODO:如果第一个TS包也是最后一个TS包,是不是需要考虑这个情况?
// MpegTsHeader最少占6个字节.(前4个走字节 + AdaptationFieldLength(1 byte) + 3个指示符5个标志位(1 byte))
if tsHeader.AdaptationFieldLength >= 1 {
tsStuffingLength = tsHeader.AdaptationFieldLength - 1
} else {
tsStuffingLength = 0
}
// error
tsHeaderLength, err = WriteTsHeader(bwTsHeader, tsHeader)
if err != nil {
return
}
if tsStuffingLength > 0 {
if _, err = bwTsHeader.Write(util.GetFillBytes(0xff, int(tsStuffingLength))); err != nil {
return
}
}
tsHeaderLength += int(tsStuffingLength)
} else {
tsHeaderLength, err = WriteTsHeader(bwTsHeader, tsHeader)
if err != nil {
return
}
}
tsPayloadLength := TS_PACKET_SIZE - tsHeaderLength
//fmt.Println("tsPayloadLength :", tsPayloadLength)
// 这里不断的减少PES包
tsHeaderByte := bwTsHeader.Bytes()
tsPayloadByte := bwPESPkt.Next(tsPayloadLength)
// tmp := tsHeaderByte[3] << 2
// tmp = tmp >> 6
// if tmp == 2 {
// fmt.Println("fuck you mother.")
// }
tsPktByte := append(tsHeaderByte, tsPayloadByte...)
if len(tsPktByte) != TS_PACKET_SIZE {
err = errors.New(fmt.Sprintf("%s, packet size=%d", "TS_PACKET_SIZE != 188,", len(tsPktByte)))
return
}
tsPkts = append(tsPkts, tsPktByte...)
}
return
}

View File

@@ -1,383 +0,0 @@
package mpegts
import (
"bytes"
"errors"
"fmt"
"github.com/langhuihui/monibuca/monica/util"
"io"
)
// ios13818-1-CN.pdf 46(60)-153(167)/page
//
// PMT
//
var DefaultPMTPacket = []byte{
// TS Header
0x47, 0x41, 0x00, 0x10,
// Pointer Field
0x00,
// PSI
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
// PMT
0xe1, 0x01,
0xf0, 0x00,
// H264
0x1b, 0xe1, 0x01, 0xf0, 0x00,
// AAC
0x0f, 0xe1, 0x02, 0xf0, 0x00,
//0x00, 0x00, 0x00, 0x00, 0x00,
// CRC for not audio
//0x00, 0x00, 0x00, 0x00,
// CRC for AAC
0x9e, 0x28, 0xc6, 0xdd,
// CRC for MP3
// 0x4e, 0x59, 0x3d, 0x1e,
// Stuffing 157 bytes
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}
// TS Header :
// SyncByte = 0x47
// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0),
// Pid = 4097(0x1001),
// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000),
// PSI :
// TableID = 0x02,
// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11),
// SectionLength = 23(0x17)
// ProgramNumber = 0x0001
// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0),
// SectionNumber = 0x00
// LastSectionNumber = 0x00
// PMT:
// Reserved3 = 15(B:1110), PcrPID = 256(0x100)
// Reserved4 = 16(B:1111), ProgramInfoLength = 0(0x000)
// H264:
// StreamType = 0x1b,
// Reserved5 = 15(B:1110), ElementaryPID = 256(0x100)
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
// AAC:
// StreamType = 0x0f,
// Reserved5 = 15(B:1110), ElementaryPID = 257(0x101)
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
type MpegTsPmtStream struct {
StreamType byte // 8 bits 指示具有 PID值的包内承载的节目元类型,其 PID值由 elementary_PID所指定
Reserved5 byte // 3 bits 保留位
ElementaryPID uint16 // 13 bits 指定承载相关节目元的传输流包的 PID
Reserved6 byte // 4 bits 保留位
EsInfoLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10比特指示紧随 ES_info_length字段的相关节目元描述符的字节数
// N Loop Descriptors
Descriptor []MpegTsDescriptor // 不确定字节数,可变
}
// Program Map Table (节目映射表)
type MpegTsPMT struct {
// PSI
TableID byte // 8 bits 0x00->PAT,0x02->PMT
SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1
Zero byte // 1 bit 0
Reserved1 byte // 2 bits 保留位
SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD)
ProgramNumber uint16 // 16 bits 指定 program_map_PID 所适用的节目
Reserved2 byte // 2 bits 保留位
VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号
CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效
SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段
LastSectionNumber byte // 8 bits 最后一个分段的号码
Reserved3 byte // 3 bits 保留位 0x07
PcrPID uint16 // 13 bits 指明TS包的PID值.该TS包含有PCR域,该PCR值对应于由节目号指定的对应节目.如果对于私有数据流的节目定义与PCR无关.这个域的值将为0x1FFF
Reserved4 byte // 4 bits 预留位 0x0F
ProgramInfoLength uint16 // 12 bits 前两位bit为00.该域指出跟随其后对节目信息的描述的byte数
ProgramInfoDescriptor []MpegTsDescriptor // N Loop Descriptors 可变 节目信息描述
// N Loop
Stream []MpegTsPmtStream // PMT表里面的所有音视频索引信息
Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值
}
func ReadPMT(r io.Reader) (pmt MpegTsPMT, err error) {
lr, psi, err := ReadPSI(r, PSI_TYPE_PMT)
if err != nil {
return
}
pmt = psi.Pmt
// reserved3(3) + pcrPID(13)
pcrPID, err := util.ReadByteToUint16(lr, true)
if err != nil {
return
}
pmt.PcrPID = pcrPID & 0x1fff
// reserved4(4) + programInfoLength(12)
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
programInfoLength, err := util.ReadByteToUint16(lr, true)
if err != nil {
return
}
pmt.ProgramInfoLength = programInfoLength & 0x3ff
// 如果length>0那么,紧跟programInfoLength后面就有length个字节
if pmt.ProgramInfoLength > 0 {
lr := &io.LimitedReader{R: lr, N: int64(pmt.ProgramInfoLength)}
pmt.ProgramInfoDescriptor, err = ReadPMTDescriptor(lr)
if err != nil {
return
}
}
// N Loop
// 开始N循环,读取所有的流的信息
for lr.N > 0 {
var streams MpegTsPmtStream
// streamType(8)
streams.StreamType, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
// reserved5(3) + elementaryPID(13)
streams.ElementaryPID, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
streams.ElementaryPID = streams.ElementaryPID & 0x1fff
// reserved6(4) + esInfoLength(12)
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
streams.EsInfoLength, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
streams.EsInfoLength = streams.EsInfoLength & 0x3ff
// 如果length>0那么,紧跟esInfoLength后面就有length个字节
if streams.EsInfoLength > 0 {
lr := &io.LimitedReader{R: lr, N: int64(streams.EsInfoLength)}
streams.Descriptor, err = ReadPMTDescriptor(lr)
if err != nil {
return
}
}
// 每读取一个流的信息(音频流或者视频流或者其他),都保存起来
pmt.Stream = append(pmt.Stream, streams)
}
if cr, ok := r.(*util.Crc32Reader); ok {
err = cr.ReadCrc32UIntAndCheck()
if err != nil {
return
}
}
return
}
func ReadPMTDescriptor(lr *io.LimitedReader) (Desc []MpegTsDescriptor, err error) {
var desc MpegTsDescriptor
for lr.N > 0 {
// tag (8)
desc.Tag, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
// length (8)
desc.Length, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
desc.Data = make([]byte, desc.Length)
_, err = lr.Read(desc.Data)
if err != nil {
return
}
Desc = append(Desc, desc)
}
return
}
func WritePMTDescriptor(w io.Writer, descs []MpegTsDescriptor) (err error) {
for _, desc := range descs {
// tag(8)
if err = util.WriteUint8ToByte(w, desc.Tag); err != nil {
return
}
// length (8)
if err = util.WriteUint8ToByte(w, uint8(len(desc.Data))); err != nil {
return
}
// data
if _, err = w.Write(desc.Data); err != nil {
return
}
}
return
}
func WritePMTBody(w io.Writer, pmt MpegTsPMT) (err error) {
// reserved3(3) + pcrPID(13)
if err = util.WriteUint16ToByte(w, pmt.PcrPID|7<<13, true); err != nil {
return
}
// programInfoDescriptor 节目信息描述,字节数不能确定
bw := &bytes.Buffer{}
if err = WritePMTDescriptor(bw, pmt.ProgramInfoDescriptor); err != nil {
return
}
pmt.ProgramInfoLength = uint16(bw.Len())
// reserved4(4) + programInfoLength(12)
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
if err = util.WriteUint16ToByte(w, pmt.ProgramInfoLength|0xf000, true); err != nil {
return
}
// programInfoDescriptor
if _, err = w.Write(bw.Bytes()); err != nil {
return
}
// 循环读取所有的流的信息(音频或者视频)
for _, esinfo := range pmt.Stream {
// streamType(8)
if err = util.WriteUint8ToByte(w, esinfo.StreamType); err != nil {
return
}
// reserved5(3) + elementaryPID(13)
if err = util.WriteUint16ToByte(w, esinfo.ElementaryPID|7<<13, true); err != nil {
return
}
// descriptor ES流信息描述,字节数不能确定
bw := &bytes.Buffer{}
if err = WritePMTDescriptor(bw, esinfo.Descriptor); err != nil {
return
}
esinfo.EsInfoLength = uint16(bw.Len())
// reserved6(4) + esInfoLength(12)
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
if err = util.WriteUint16ToByte(w, esinfo.EsInfoLength|0xf000, true); err != nil {
return
}
// descriptor
if _, err = w.Write(bw.Bytes()); err != nil {
return
}
}
return
}
func WritePMT(w io.Writer, pmt MpegTsPMT) (err error) {
bw := &bytes.Buffer{}
if err = WritePMTBody(bw, pmt); err != nil {
return
}
if pmt.SectionLength == 0 {
pmt.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes()))
}
psi := MpegTsPSI{}
psi.Pmt = pmt
if err = WritePSI(w, PSI_TYPE_PMT, psi, bw.Bytes()); err != nil {
return
}
return
}
func WritePMTPacket(w io.Writer, tsHeader []byte, pmt MpegTsPMT) (err error) {
if pmt.TableID != TABLE_TSPMS {
err = errors.New("PMT table ID error")
return
}
// 将所有要写的数据(PMT),全部放入到buffer中去.
// buffer 里面已经写好了整个PMT表(PointerField+PSI+PMT+CRC)
bw := &bytes.Buffer{}
if err = WritePMT(bw, pmt); err != nil {
return
}
// TODO:如果Pmt.Stream里面包含的信息很大,大于188?
stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len())
var PMTPacket []byte
PMTPacket = append(PMTPacket, tsHeader...)
PMTPacket = append(PMTPacket, bw.Bytes()...)
PMTPacket = append(PMTPacket, stuffingBytes...)
fmt.Println("-------------------------")
fmt.Println("Write PMT :", PMTPacket)
fmt.Println("-------------------------")
// 写PMT负载
if _, err = w.Write(PMTPacket); err != nil {
return
}
return
}
func WriteDefaultPMTPacket(w io.Writer) (err error) {
_, err = w.Write(DefaultPMTPacket)
if err != nil {
return
}
return
}

View File

@@ -1,231 +0,0 @@
package mpegts
import (
"errors"
"fmt"
"github.com/langhuihui/monibuca/monica/util"
"io"
"io/ioutil"
)
//
// PSI
//
const (
PSI_TYPE_PAT = 1
PSI_TYPE_PMT = 2
PSI_TYPE_NIT = 3
PSI_TYPE_CAT = 4
PSI_TYPE_TST = 5
PSI_TYPE_IPMP_CIT = 6
)
type MpegTsPSI struct {
// PAT
// PMT
// CAT
// NIT
Pat MpegTsPAT
Pmt MpegTsPMT
}
// 当传输流包有效载荷包含 PSI 数据时,payload_unit_start_indicator 具有以下意义:
// 若传输流包承载 PSI分段的首字节,则 payload_unit_start_indicator 值必为 1,指示此传输流包的有效载荷的首字节承载pointer_field.
// 若传输流包不承载 PSI 分段的首字节,则 payload_unit_start_indicator 值必为 0,指示在此有效载荷中不存在 pointer_field
// 只要是PSI就一定会有pointer_field
func ReadPSI(r io.Reader, pt uint32) (lr *io.LimitedReader, psi MpegTsPSI, err error) {
// pointer field(8)
cr, ok := r.(*util.Crc32Reader)
if ok {
r = cr.R
}
pointer_field, err := util.ReadByteToUint8(r)
if err != nil {
return
}
if pointer_field != 0 {
// 无论如何都应该确保能将pointer_field读取到,并且io.Reader指针向下移动
// ioutil.Discard常用在,http中,如果Get请求,获取到了很大的Body,要丢弃Body,就用这个方法.
// 因为http默认重链接的时候,必须等body读取完成.
// 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据
if _, err = io.CopyN(ioutil.Discard, r, int64(pointer_field)); err != nil {
return
}
}
if ok {
r = cr
}
// table id(8)
tableId, err := util.ReadByteToUint8(r)
if err != nil {
return
}
// sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
// sectionLength 前两个字节固定为00
sectionSyntaxIndicatorAndSectionLength, err := util.ReadByteToUint16(r, true)
if err != nil {
return
}
// 指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC
// 因此剩下最多只能在读sectionLength长度的字节
lr = &io.LimitedReader{R: r, N: int64(sectionSyntaxIndicatorAndSectionLength & 0x3FF)}
// PAT TransportStreamID(16) or PMT ProgramNumber(16)
transportStreamIdOrProgramNumber, err := util.ReadByteToUint16(lr, true)
if err != nil {
return
}
// reserved2(2) + versionNumber(5) + currentNextIndicator(1)
versionNumberAndCurrentNextIndicator, err := util.ReadByteToUint8(lr)
if err != nil {
return
}
// sectionNumber(8)
sectionNumber, err := util.ReadByteToUint8(lr)
if err != nil {
return
}
// lastSectionNumber(8)
lastSectionNumber, err := util.ReadByteToUint8(lr)
if err != nil {
return
}
// 因为lr.N是从sectionLength开始计算,所以要减去 pointer_field(8) + table id(8) + sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
lr.N -= 4
switch pt {
case PSI_TYPE_PAT:
{
if tableId != TABLE_PAS {
err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId))
return
}
psi.Pat.TableID = tableId
psi.Pat.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15)
psi.Pat.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF
psi.Pat.TransportStreamID = transportStreamIdOrProgramNumber
psi.Pat.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e
psi.Pat.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01
psi.Pat.SectionNumber = sectionNumber
psi.Pat.LastSectionNumber = lastSectionNumber
}
case PSI_TYPE_PMT:
{
if tableId != TABLE_TSPMS {
err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId))
return
}
psi.Pmt.TableID = tableId
psi.Pmt.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15)
psi.Pmt.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF
psi.Pmt.ProgramNumber = transportStreamIdOrProgramNumber
psi.Pmt.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e
psi.Pmt.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01
psi.Pmt.SectionNumber = sectionNumber
psi.Pmt.LastSectionNumber = lastSectionNumber
}
}
return
}
func WritePSI(w io.Writer, pt uint32, psi MpegTsPSI, data []byte) (err error) {
var tableId, versionNumberAndCurrentNextIndicator, sectionNumber, lastSectionNumber uint8
var sectionSyntaxIndicatorAndSectionLength, transportStreamIdOrProgramNumber uint16
switch pt {
case PSI_TYPE_PAT:
{
if psi.Pat.TableID != TABLE_PAS {
err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 0", tableId))
return
}
tableId = psi.Pat.TableID
sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pat.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pat.SectionLength
transportStreamIdOrProgramNumber = psi.Pat.TransportStreamID
versionNumberAndCurrentNextIndicator = psi.Pat.VersionNumber<<1 | psi.Pat.CurrentNextIndicator
sectionNumber = psi.Pat.SectionNumber
lastSectionNumber = psi.Pat.LastSectionNumber
}
case PSI_TYPE_PMT:
{
if psi.Pmt.TableID != TABLE_TSPMS {
err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 2", tableId))
return
}
tableId = psi.Pmt.TableID
sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pmt.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pmt.SectionLength
transportStreamIdOrProgramNumber = psi.Pmt.ProgramNumber
versionNumberAndCurrentNextIndicator = psi.Pmt.VersionNumber<<1 | psi.Pmt.CurrentNextIndicator
sectionNumber = psi.Pmt.SectionNumber
lastSectionNumber = psi.Pmt.LastSectionNumber
}
}
// pointer field(8)
if err = util.WriteUint8ToByte(w, 0); err != nil {
return
}
cw := &util.Crc32Writer{W: w, Crc32: 0xffffffff}
// table id(8)
if err = util.WriteUint8ToByte(cw, tableId); err != nil {
return
}
// sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
// sectionLength 前两个字节固定为00
// 1 0 11 sectionLength
if err = util.WriteUint16ToByte(cw, sectionSyntaxIndicatorAndSectionLength, true); err != nil {
return
}
// PAT TransportStreamID(16) or PMT ProgramNumber(16)
if err = util.WriteUint16ToByte(cw, transportStreamIdOrProgramNumber, true); err != nil {
return
}
// reserved2(2) + versionNumber(5) + currentNextIndicator(1)
// 0x3 << 6 -> 1100 0000
// 0x3 << 6 | 1 -> 1100 0001
if err = util.WriteUint8ToByte(cw, versionNumberAndCurrentNextIndicator); err != nil {
return
}
// sectionNumber(8)
if err = util.WriteUint8ToByte(cw, sectionNumber); err != nil {
return
}
// lastSectionNumber(8)
if err = util.WriteUint8ToByte(cw, lastSectionNumber); err != nil {
return
}
// data
if _, err = cw.Write(data); err != nil {
return
}
// crc32
crc32 := util.BigLittleSwap(uint(cw.Crc32))
if err = util.WriteUint32ToByte(cw, uint32(crc32), true); err != nil {
return
}
return
}

View File

@@ -1,214 +0,0 @@
package avformat
import (
"bytes"
"github.com/langhuihui/monibuca/monica/util/bits"
)
type SPSInfo struct {
ProfileIdc uint
LevelIdc uint
MbWidth uint
MbHeight uint
CropLeft uint
CropRight uint
CropTop uint
CropBottom uint
Width uint
Height uint
}
func ParseSPS(data []byte) (self SPSInfo, err error) {
r := &bits.GolombBitReader{R: bytes.NewReader(data)}
if _, err = r.ReadBits(8); err != nil {
return
}
if self.ProfileIdc, err = r.ReadBits(8); err != nil {
return
}
// constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits
if _, err = r.ReadBits(8); err != nil {
return
}
// level_idc
if self.LevelIdc, err = r.ReadBits(8); err != nil {
return
}
// seq_parameter_set_id
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if self.ProfileIdc == 100 || self.ProfileIdc == 110 ||
self.ProfileIdc == 122 || self.ProfileIdc == 244 ||
self.ProfileIdc == 44 || self.ProfileIdc == 83 ||
self.ProfileIdc == 86 || self.ProfileIdc == 118 {
var chroma_format_idc uint
if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if chroma_format_idc == 3 {
// residual_colour_transform_flag
if _, err = r.ReadBit(); err != nil {
return
}
}
// bit_depth_luma_minus8
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
// bit_depth_chroma_minus8
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
// qpprime_y_zero_transform_bypass_flag
if _, err = r.ReadBit(); err != nil {
return
}
var seq_scaling_matrix_present_flag uint
if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil {
return
}
if seq_scaling_matrix_present_flag != 0 {
for i := 0; i < 8; i++ {
var seq_scaling_list_present_flag uint
if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil {
return
}
if seq_scaling_list_present_flag != 0 {
var sizeOfScalingList uint
if i < 6 {
sizeOfScalingList = 16
} else {
sizeOfScalingList = 64
}
lastScale := uint(8)
nextScale := uint(8)
for j := uint(0); j < sizeOfScalingList; j++ {
if nextScale != 0 {
var delta_scale uint
if delta_scale, err = r.ReadSE(); err != nil {
return
}
nextScale = (lastScale + delta_scale + 256) % 256
}
if nextScale != 0 {
lastScale = nextScale
}
}
}
}
}
}
// log2_max_frame_num_minus4
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
var pic_order_cnt_type uint
if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if pic_order_cnt_type == 0 {
// log2_max_pic_order_cnt_lsb_minus4
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
} else if pic_order_cnt_type == 1 {
// delta_pic_order_always_zero_flag
if _, err = r.ReadBit(); err != nil {
return
}
// offset_for_non_ref_pic
if _, err = r.ReadSE(); err != nil {
return
}
// offset_for_top_to_bottom_field
if _, err = r.ReadSE(); err != nil {
return
}
var num_ref_frames_in_pic_order_cnt_cycle uint
if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil {
return
}
for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ {
if _, err = r.ReadSE(); err != nil {
return
}
}
}
// max_num_ref_frames
if _, err = r.ReadExponentialGolombCode(); err != nil {
return
}
// gaps_in_frame_num_value_allowed_flag
if _, err = r.ReadBit(); err != nil {
return
}
if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil {
return
}
self.MbWidth++
if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil {
return
}
self.MbHeight++
var frame_mbs_only_flag uint
if frame_mbs_only_flag, err = r.ReadBit(); err != nil {
return
}
if frame_mbs_only_flag == 0 {
// mb_adaptive_frame_field_flag
if _, err = r.ReadBit(); err != nil {
return
}
}
// direct_8x8_inference_flag
if _, err = r.ReadBit(); err != nil {
return
}
var frame_cropping_flag uint
if frame_cropping_flag, err = r.ReadBit(); err != nil {
return
}
if frame_cropping_flag != 0 {
if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil {
return
}
if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil {
return
}
}
self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2
self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2
return
}

View File

@@ -1,34 +0,0 @@
package monica
import "log"
const (
PLUGIN_SUBSCRIBER = 1
PLUGIN_PUBLISHER = 1 << 1
PLUGIN_HOOK = 1 << 2
)
var (
cg = &Config{Plugins: make(map[string]interface{})}
plugins = make(map[string]*PluginConfig)
)
type PluginConfig struct {
Name string //插件名称
Type byte //类型
Config interface{} //插件配置
Run func()
}
type Config struct {
Plugins map[string]interface{}
}
func InstallPlugin(opt *PluginConfig) {
log.Printf("install plugin %s", opt.Name)
plugins[opt.Name] = opt
}
type ListenerConfig struct {
ListenAddr string
}

View File

@@ -1,69 +0,0 @@
package monica
var AuthHooks = make(AuthHook, 0)
type AuthHook []func(string) error
func (h AuthHook) AddHook(hook func(string) error) {
AuthHooks = append(h, hook)
}
func (h AuthHook) Trigger(sign string) error {
for _, h := range h {
if err := h(sign); err != nil {
return err
}
}
return nil
}
var OnPublishHooks = make(OnPublishHook, 0)
type OnPublishHook []func(r *Room)
func (h OnPublishHook) AddHook(hook func(r *Room)) {
OnPublishHooks = append(h, hook)
}
func (h OnPublishHook) Trigger(r *Room) {
for _, h := range h {
h(r)
}
}
var OnSubscribeHooks = make(OnSubscribeHook, 0)
type OnSubscribeHook []func(s *OutputStream)
func (h OnSubscribeHook) AddHook(hook func(s *OutputStream)) {
OnSubscribeHooks = append(h, hook)
}
func (h OnSubscribeHook) Trigger(s *OutputStream) {
for _, h := range h {
h(s)
}
}
var OnDropHooks = make(OnDropHook, 0)
type OnDropHook []func(s *OutputStream)
func (h OnDropHook) AddHook(hook func(s *OutputStream)) {
OnDropHooks = append(h, hook)
}
func (h OnDropHook) Trigger(s *OutputStream) {
for _, h := range h {
h(s)
}
}
var OnSummaryHooks = make(OnSummaryHook, 0)
type OnSummaryHook []func(bool)
func (h OnSummaryHook) AddHook(hook func(bool)) {
OnSummaryHooks = append(h, hook)
}
func (h OnSummaryHook) Trigger(v bool) {
for _, h := range h {
h(v)
}
}

View File

@@ -1,34 +0,0 @@
package monica
import (
"encoding/json"
"github.com/BurntSushi/toml"
"io/ioutil"
"log"
)
var ConfigRaw []byte
func Run(configFile string) (err error) {
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
return
}
go Summary.StartSummary()
if _, err = toml.Decode(string(ConfigRaw), cg); err == nil {
for name, config := range plugins {
if cfg, ok := cg.Plugins[name]; ok {
b, _ := json.Marshal(cfg)
if err = json.Unmarshal(b, config.Config); err != nil {
log.Println(err)
continue
}
} else if config.Config != nil {
continue
}
if config.Run != nil {
go config.Run()
}
}
}
return
}

View File

@@ -1,32 +0,0 @@
package monica
import (
"io"
"log"
)
type LogWriter struct {
io.Writer
origin io.Writer
}
func (w *LogWriter) Write(data []byte) (n int, err error) {
if n, err = w.Writer.Write(data); err != nil {
go log.SetOutput(w.origin)
}
return w.origin.Write(data)
}
func AddWriter(wn io.Writer) {
log.SetOutput(&LogWriter{
Writer: wn,
origin: log.Writer(),
})
}
func MayBeError(info error) (hasError bool) {
if hasError = info != nil; hasError {
log.Print(info)
}
return
}

Some files were not shown because too many files have changed in this diff Show More