├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── algo ├── chan.go ├── map.go └── ring.go ├── app ├── configuration.go ├── options.go └── plugins.go ├── codecs ├── adts.go ├── avpacket.go ├── define.go ├── flv.go ├── h264.go ├── mp4.go ├── mpegts │ ├── mpegts.go │ ├── mpegts.md │ ├── mpegts_crc32.go │ ├── mpegts_pat.go │ ├── mpegts_pes.go │ ├── mpegts_pmt.go │ └── mpegts_psi.go └── sps.go ├── config.yaml ├── cspell.json ├── go.mod ├── go.sum ├── main.go ├── media ├── dtse.go ├── frame.go ├── ring.go ├── stream.go └── track.go ├── process.go ├── proto ├── README.md └── session │ └── session.proto ├── pubsub ├── channel.go ├── define.go ├── options.go ├── publisher.go ├── puller.go ├── pusher.go ├── stream.go └── subscriber.go ├── runtime ├── arch.gif ├── hook.go ├── logger.go ├── media.go ├── nalu.go ├── options.go ├── pool │ └── slice_pool.go ├── publisher.go ├── ring.go ├── runtime.go ├── subscriber.go └── summary.go ├── service ├── auth │ └── readme.md ├── rtp │ └── README.md ├── rtsp │ └── readme.md └── webrtc │ ├── profiles.go │ ├── publisher.go │ ├── session.go │ ├── subscriber.go │ └── webrtc.go ├── track ├── audio.go ├── define.go └── video.go ├── util ├── SSE.go ├── big_little_endian.go ├── bits │ ├── bits.go │ ├── bits_test.go │ ├── bufio │ │ └── bufio.go │ ├── golomb_reader.go │ └── pio │ │ ├── pio.go │ │ ├── reader.go │ │ ├── vec.go │ │ ├── vec_test.go │ │ └── writer.go ├── convert.go ├── crc32.go ├── stderr.go ├── sys.go └── vecio.go └── ws ├── connection.go ├── server.go └── session ├── client.go ├── connection.go └── manager.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | *.exe 24 | .idea 25 | resource 26 | *.log 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.tabSize": 2, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": true 6 | }, 7 | "go.useCodeSnippetsOnFunctionSuggest": false, 8 | "go.testFlags": [ 9 | "-v", 10 | "-count=1" 11 | ], 12 | "go.useLanguageServer": true, 13 | "go.lintTool": "golangci-lint", 14 | "go.lintOnSave": "package", 15 | "go.lintFlags": [ 16 | "--fast" 17 | ], 18 | "go.vetOnSave": "package", 19 | "go.testTimeout": "10s", 20 | "go.formatTool": "goimports", 21 | "go.delveConfig": { 22 | "dlvLoadConfig": { 23 | "followPointers": true, 24 | "maxVariableRecurse": 1, 25 | "maxStringLen": 64, 26 | "maxArrayValues": 64, 27 | "maxStructFields": -1 28 | }, 29 | "apiVersion": 2, 30 | "showGlobalVariables": true 31 | }, 32 | "go.editorContextMenuCommands": { 33 | "toggleTestFile": true, 34 | "addTags": true, 35 | "removeTags": true, 36 | "testAtCursor": true, 37 | "testFile": true, 38 | "testPackage": true, 39 | "generateTestForFunction": true, 40 | "generateTestForFile": true, 41 | "generateTestForPackage": true, 42 | "addImport": true, 43 | "testCoverage": true, 44 | "playground": true, 45 | "debugTestAtCursor": true 46 | }, 47 | "go.playground": { 48 | "openbrowser": false, 49 | "share": false, 50 | "run": false 51 | }, 52 | "go.addTags": { 53 | "tags": "json", 54 | "options": "json=omitempty", 55 | "promptForTags": true, 56 | "transform": "snakecase" 57 | }, 58 | "go.removeTags": { 59 | "tags": "", 60 | "options": "", 61 | "promptForTags": false 62 | }, 63 | "cSpell.allowCompoundWords": true, 64 | // Proto 65 | "clang-format.style": "google", 66 | "clang-format.executable": "C:/Program Files/LLVM/bin/clang-format.exe", 67 | "protoc": { 68 | "options": [ 69 | "--proto_path=${workspaceRoot}", 70 | "--proto_path=${env.GOPATH}/src", 71 | ] 72 | }, 73 | // 头部注释 74 | "fileheader.customMade": { 75 | "Author": "git config user.name && git config user.email", 76 | "Date": "Do not edit", 77 | "LastEditors": "git config user.name && git config user.email", 78 | "LastEditTime": "Do not edit", // 文件最后编辑时间 79 | "FilePath": "Do not edit", // 文件在项目中的相对路径 自动更新 80 | "Description": "code content ", // 介绍文件的作用、文件的入参、出参。 81 | // 版权声明获取git配置, 与Author字段一致: ${git_name} ${git_email} ${git_name_email} 82 | "custom_string_obkoro1_copyright": "Copyright (c) ${now_year} by ${git_name_email}, All Rights Reserved. " 83 | }, 84 | // 函数注释 85 | "fileheader.cursorMode": { 86 | "description": "", // 函数注释生成之后,光标移动到这里 87 | "param": "", // param 开启函数参数自动提取 需要将光标放在函数行或者函数上方的空白行 88 | "return": "", 89 | } 90 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present, crazybber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # instruction 2 | 3 | a streaming service built on go-micro ecosystem 4 | 5 | ## Features 6 | 7 | - AVCodec: H264 8 | - Stream: TS/FLV/MP4 And PS in future 9 | - Session: RTSP/RTMP/HLV/GRPC 10 | 11 | ## Inspired By 12 | 13 | - [monibuca](https://github.com/monibuca/engine) 14 | - [websocket-streamserver](https://github.com/use-go/websocket-streamserver) 15 | - [websocket-mse](https://github.com/elsampsa/websocket-mse-demo) 16 | - [EasyDarwinGo](https://github.com/GB28181/EasyDarwinGo) 17 | 18 | ## License 19 | 20 | MIT 21 | -------------------------------------------------------------------------------- /algo/chan.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-06 11:05:32 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-08 21:47:37 6 | * @FilePath: \stream\algo\chan.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package algo 11 | 12 | import ( 13 | "math" 14 | "sync/atomic" 15 | ) 16 | 17 | // SafeChan for safety channel,可以防止close后被写入的问题 18 | type SafeChan[T any] struct { 19 | C chan T 20 | senders int32 // senders counts 21 | len int 22 | } 23 | 24 | // Init a n length chan 25 | func (sc *SafeChan[T]) Init(n int) { 26 | sc.C = make(chan T, n) 27 | sc.len = n 28 | } 29 | 30 | // Close chan safely senders 31 | func (sc *SafeChan[T]) Close() bool { 32 | if atomic.CompareAndSwapInt32(&sc.senders, 0, math.MinInt32) { 33 | close(sc.C) 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | func (sc *SafeChan[T]) Send(v T) bool { 40 | // senders增加后为正数说明有channel未被关闭,可以发送数据 41 | if atomic.AddInt32(&sc.senders, 1) > 0 { 42 | sc.C <- v 43 | //发送后计数减1 44 | atomic.AddInt32(&sc.senders, -1) 45 | return true 46 | } 47 | return false 48 | } 49 | 50 | func (sc *SafeChan[T]) IsClosed() bool { 51 | return atomic.LoadInt32(&sc.senders) < 0 52 | } 53 | 54 | func (sc *SafeChan[T]) IsEmpty() bool { 55 | return atomic.LoadInt32(&sc.senders) == 0 56 | } 57 | 58 | // IsFull return sender exist 59 | func (sc *SafeChan[T]) IsFull() bool { 60 | return atomic.LoadInt32(&sc.senders) > 0 61 | } 62 | 63 | func (sc *SafeChan[T]) ChanCount() int { 64 | return sc.len 65 | } 66 | -------------------------------------------------------------------------------- /algo/map.go: -------------------------------------------------------------------------------- 1 | package algo 2 | 3 | import "sync" 4 | 5 | type Map[K comparable, V any] struct { 6 | sync.RWMutex 7 | Map map[K]V 8 | } 9 | -------------------------------------------------------------------------------- /algo/ring.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:32 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-02 16:51:15 6 | * @FilePath: \stream\algo\ring.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package algo 11 | 12 | // Ring for anytype 13 | type Ring[T any] struct { 14 | next, prev *Ring[T] 15 | Value T // used by client 16 | } 17 | 18 | func (r *Ring[T]) init() *Ring[T] { 19 | r.next = r 20 | r.prev = r 21 | return r 22 | } 23 | -------------------------------------------------------------------------------- /app/configuration.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Parameters for engine 8 | type Parameters struct { 9 | EnableWaitStream bool 10 | RingSize int 11 | EnableAudio bool 12 | EnableVideo bool 13 | PublishTimeout time.Duration 14 | } 15 | 16 | // ExtendInfo for extension 17 | type ExtendInfo struct { 18 | Version *string 19 | StartTime time.Time //启动时间 20 | Params *Parameters 21 | } 22 | 23 | // Settings for engine 24 | var ( 25 | Config = &Parameters{true, 10, false, true, time.Minute} 26 | 27 | // ConfigRaw 配置信息的原始数据 28 | ConfigRaw []byte 29 | // Version 引擎版本号 30 | Version string 31 | // AppInfo 引擎信息 32 | AppInfo = &ExtendInfo{&Version, time.Now(), Config} 33 | ) 34 | 35 | // Configuration from files 36 | type Configuration struct { 37 | Params Parameters 38 | global struct { 39 | console struct { 40 | secrets string 41 | } 42 | } 43 | webrtc struct { 44 | enable bool 45 | } 46 | 47 | rtsp struct { 48 | enable bool 49 | pull struct { 50 | pullstart bool 51 | pulllist map[string]string 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:32 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 16:53:11 6 | * @FilePath: \stream\app\options.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package app 11 | 12 | import ( 13 | "context" 14 | "net/http" 15 | "time" 16 | 17 | "github.com/micro-community/stream/pubsub" 18 | webrtc3 "github.com/pion/webrtc/v3" 19 | ) 20 | 21 | type WebRTCOption struct { 22 | //config.Publish 23 | //config.Subscribe 24 | Name string 25 | ICEServers []string 26 | PublicIP []string 27 | PortMin uint16 28 | PortMax uint16 29 | PLI time.Duration 30 | ME webrtc3.MediaEngine 31 | SE webrtc3.SettingEngine 32 | API *webrtc3.API 33 | } 34 | 35 | // PluginConfig 插件配置定义 36 | type PluginOptions struct { 37 | context.Context `json:"-"` 38 | context.CancelFunc `json:"-"` 39 | Name string //插件名称 40 | Type byte //类型 41 | Version string //插件版本 42 | webrtc WebRTCOption 43 | } 44 | 45 | type Option func(*PluginOptions) 46 | 47 | func WithWebRTC(opts WebRTCOption) Option { 48 | return func(po *PluginOptions) { 49 | po.webrtc = opts 50 | } 51 | 52 | } 53 | 54 | type WebOption struct { 55 | ListenAddr string 56 | ListenAddrTLS string 57 | CertFile string 58 | KeyFile string 59 | CORS bool //是否自动添加CORS头 60 | UserName string 61 | Password string 62 | mux *http.ServeMux 63 | } 64 | 65 | type ConsoleOption struct { 66 | Server string //远程控制台地址 67 | Secret string //远程控制台密钥 68 | PublicAddr string //公网地址,提供远程控制台访问的地址,不配置的话使用自动识别的地址 69 | PublicAddrTLS string 70 | } 71 | 72 | // engine options 73 | type EngineOption struct { 74 | Publish pubsub.PublishOption 75 | Subscribe pubsub.SubscribeOption 76 | HTTP WebOption 77 | Console ConsoleOption 78 | RTPReorder bool 79 | EnableAVCC bool //启用AVCC格式,rtmp协议使用 80 | EnableRTP bool //启用RTP格式,rtsp、gb18181等协议使用 81 | LogLevel string 82 | } 83 | -------------------------------------------------------------------------------- /app/plugins.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | 7 | "github.com/logrusorgru/aurora" 8 | "github.com/micro-community/stream/pubsub" 9 | "go-micro.dev/v4/util/log" 10 | ) 11 | 12 | type IPlugin interface { 13 | // 可能的入参类型:FirstConfig 第一次初始化配置,Config 后续配置更新,SE系列(StateEvent)流状态变化事件 14 | //生命周期事件: 15 | //Init() 16 | //Update(opts PluginOptions) //配置更新 17 | //State Change 18 | OnEvent(any) 19 | Publish(streamPath string, pub pubsub.IPublish) error 20 | Subscribe(streamPath string, sub pubsub.ISubscribe) error 21 | Push(streamPath string, url string, pusher pubsub.IPush, save bool) 22 | } 23 | 24 | type plugin struct { 25 | Opts PluginOptions 26 | once sync.Once 27 | } 28 | 29 | // Plugins 所有的插件配置 30 | var Plugins = make(map[string]IPlugin) 31 | 32 | // Install 安装功能组件 33 | func Install(opts ...Option) IPlugin { 34 | 35 | pluginOptions := PluginOptions{ 36 | Name: "DefaultName", 37 | Version: "0.0.1", 38 | } 39 | for _, o := range opts { 40 | o(&pluginOptions) 41 | } 42 | //创建组件 43 | plug := &plugin{ 44 | Opts: pluginOptions, 45 | once: sync.Once{}, 46 | } 47 | 48 | //初始化组件 49 | log.Info(aurora.Green("install plugin"), aurora.BrightCyan(plug.Opts.Name), aurora.BrightBlue(plug.Opts.Version)) 50 | 51 | return plug 52 | } 53 | 54 | func (p *plugin) OnEvent(any) { 55 | 56 | } 57 | 58 | func (p *plugin) handleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { 59 | 60 | } 61 | 62 | // run plugin 63 | func (p *plugin) run() { 64 | 65 | } 66 | 67 | func (p *plugin) Update(opts PluginOptions) { 68 | 69 | } 70 | 71 | func (p *plugin) Push(streamPath string, url string, pusher pubsub.IPush, save bool) { 72 | 73 | } 74 | 75 | func (p *plugin) Publish(streamPath string, pub pubsub.IPublish) error { 76 | 77 | return nil 78 | } 79 | 80 | func (p *plugin) Subscribe(streamPath string, sub pubsub.ISubscribe) error { 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /codecs/adts.go: -------------------------------------------------------------------------------- 1 | package codecs 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/micro-community/stream/util" 7 | ) 8 | 9 | // ISO/IEC 14496-15 11(16)/page 10 | // 11 | // Advanced Video Coding 12 | // 13 | 14 | type AVCDecoderConfigurationRecord struct { 15 | ConfigurationVersion byte // 8 bits Version 16 | AVCProfileIndication byte // 8 bits 17 | ProfileCompatibility byte // 8 bits 18 | AVCLevelIndication byte // 8 bits 19 | Reserved1 byte // 6 bits 20 | LengthSizeMinusOne byte // 2 bits 非常重要,每个NALU包前面都(lengthSizeMinusOne & 3)+1个字节的NAL包长度描述 21 | Reserved2 byte // 3 bits 22 | NumOfSequenceParameterSets byte // 5 bits SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F 23 | NumOfPictureParameterSets byte // 8 bits PPS 的个数 24 | 25 | SequenceParameterSetLength uint16 // 16 byte SPS Length 26 | SequenceParameterSetNALUnit []byte // n byte SPS 27 | PictureParameterSetLength uint16 // 16 byte PPS Length 28 | PictureParameterSetNALUnit []byte // n byte PPS 29 | } 30 | 31 | // func (p *AVCDecoderConfigurationRecord) Marshal(b []byte) (n int) { 32 | // b[0] = 1 33 | // b[1] = p.AVCProfileIndication 34 | // b[2] = p.ProfileCompatibility 35 | // b[3] = p.AVCLevelIndication 36 | // b[4] = p.LengthSizeMinusOne | 0xfc 37 | // b[5] = uint8(len(p.SPS)) | 0xe0 38 | // n += 6 39 | // 40 | // for _, sps := range p.SPS { 41 | // pio.PutU16BE(b[n:], uint16(len(sps))) 42 | // n += 2 43 | // copy(b[n:], sps) 44 | // n += len(sps) 45 | // } 46 | // 47 | // b[n] = uint8(len(p.PPS)) 48 | // n++ 49 | // 50 | // for _, pps := range p.PPS { 51 | // pio.PutU16BE(b[n:], uint16(len(pps))) 52 | // n += 2 53 | // copy(b[n:], pps) 54 | // n += len(pps) 55 | // } 56 | // 57 | // return 58 | // } 59 | var ErrDecconfInvalid = errors.New("decode error") 60 | 61 | func (p *AVCDecoderConfigurationRecord) Unmarshal(b []byte) (n int, err error) { 62 | if len(b) < 7 { 63 | err = errors.New("not enough len") 64 | return 65 | } 66 | 67 | p.AVCProfileIndication = b[1] 68 | p.ProfileCompatibility = b[2] 69 | p.AVCLevelIndication = b[3] 70 | p.LengthSizeMinusOne = b[4] & 0x03 71 | spscount := int(b[5] & 0x1f) 72 | n += 6 73 | var sps, pps [][]byte 74 | for i := 0; i < spscount; i++ { 75 | if len(b) < n+2 { 76 | err = ErrDecconfInvalid 77 | return 78 | } 79 | spslen := int(util.BigEndian.Uint16(b[n:])) 80 | n += 2 81 | 82 | if len(b) < n+spslen { 83 | err = ErrDecconfInvalid 84 | return 85 | } 86 | sps = append(sps, b[n:n+spslen]) 87 | n += spslen 88 | } 89 | p.SequenceParameterSetLength = uint16(len(sps[0])) 90 | p.SequenceParameterSetNALUnit = sps[0] 91 | if len(b) < n+1 { 92 | err = ErrDecconfInvalid 93 | return 94 | } 95 | ppscount := int(b[n]) 96 | n++ 97 | 98 | for i := 0; i < ppscount; i++ { 99 | if len(b) < n+2 { 100 | err = ErrDecconfInvalid 101 | return 102 | } 103 | ppslen := int(util.BigEndian.Uint16(b[n:])) 104 | n += 2 105 | 106 | if len(b) < n+ppslen { 107 | err = ErrDecconfInvalid 108 | return 109 | } 110 | pps = append(pps, b[n:n+ppslen]) 111 | n += ppslen 112 | } 113 | p.PictureParameterSetLength = uint16(len(pps[0])) 114 | p.PictureParameterSetNALUnit = pps[0] 115 | return 116 | } 117 | 118 | // ISO/IEC 14496-3 38(52)/page 119 | // 120 | // Audio 121 | // 122 | 123 | type AudioSpecificConfig struct { 124 | AudioObjectType byte // 5 bits 125 | SamplingFrequencyIndex byte // 4 bits 126 | ChannelConfiguration byte // 4 bits 127 | GASpecificConfig 128 | } 129 | 130 | type GASpecificConfig struct { 131 | FrameLengthFlag byte // 1 bit 132 | DependsOnCoreCoder byte // 1 bit 133 | ExtensionFlag byte // 1 bit 134 | } 135 | 136 | // 137 | // AudioObjectTypes -> ISO/IEC 14496-3 43(57)/page 138 | // 139 | // 1 AAC MAIN ISO/IEC 14496-3 subpart 4 140 | // 2 AAC LC ISO/IEC 14496-3 subpart 4 141 | // 3 AAC SSR ISO/IEC 14496-3 subpart 4 142 | // 4 AAC LTP ISO/IEC 14496-3 subpart 4 143 | // 144 | // 145 | 146 | // ISO/IEC 13838-7 20(25)/page 147 | // 148 | // # Advanced Audio Coding 149 | // 150 | // AudioDataTransportStream 151 | type ADTS struct { 152 | ADTSFixedHeader 153 | ADTSVariableHeader 154 | } 155 | 156 | // 28 bits 157 | type ADTSFixedHeader struct { 158 | SyncWord uint16 // 12 bits The bit string ‘1111 1111 1111’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8) 159 | ID byte // 1 bit MPEG identifier, set to ‘1’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8) 160 | Layer byte // 2 bits Indicates which layer is used. Set to ‘00’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8) 161 | 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) 162 | Profile byte // 2 bits profile used. See clause 2 (Table 8) 163 | SamplingFrequencyIndex byte // 4 bits indicates the sampling frequency used according to the followingtable (Table 8) 164 | PrivateBit byte // 1 bit see ISO/IEC 11172-3, subclause 2.4.2.3 (Table 8) 165 | 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) 166 | OriginalCopy byte // 1 bit see ISO/IEC 11172-3, definition of data element copyright 167 | Home byte // 1 bit see ISO/IEC 11172-3, definition of data element original/copy 168 | } 169 | 170 | // SyncWord, 同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始 171 | // ID, MPEG Version: 0 for MPEG-4, 1 for MPEG-2 172 | // Layer, always: '00' 173 | // ProtectionAbsent, 表示是否误码校验 174 | // Profile, 表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种. 175 | // SamplingFrequencyIndex, 表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值 176 | // PrivateBit, 177 | // ChannelConfiguration, 表示声道数 178 | // OriginalCopy, 179 | // Home, 180 | 181 | // Profile: 182 | // 183 | // 0: Main profile 184 | // 1: Low Complexity profile(LC) 185 | // 2: Scalable Sampling Rate profile(SSR) 186 | // 3: Reserved 187 | var SamplingFrequencies = [...]int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0} 188 | 189 | // Sampling Frequencies[]: 190 | // 191 | // 0: 96000 Hz 192 | // 1: 88200 Hz 193 | // 2: 64000 Hz 194 | // 3: 48000 Hz 195 | // 4: 44100 Hz 196 | // 5: 32000 Hz 197 | // 6: 24000 Hz 198 | // 7: 22050 Hz 199 | // 8: 16000 Hz 200 | // 9: 12000 Hz 201 | // 10: 11025 Hz 202 | // 11: 8000 Hz 203 | // 12: 7350 Hz 204 | // 13: Reserved 205 | // 14: Reserved 206 | // 15: frequency is written explictly 207 | // 208 | 209 | // ChannelConfiguration: 210 | // 211 | // 0: Defined in AOT Specifc Config 212 | // 1: 1 channel: front-center 213 | // 2: 2 channels: front-left, front-right 214 | // 3: 3 channels: front-center, front-left, front-right 215 | // 4: 4 channels: front-center, front-left, front-right, back-center 216 | // 5: 5 channels: front-center, front-left, front-right, back-left, back-right 217 | // 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel 218 | // 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel 219 | // 8-15: Reserved 220 | // 221 | 222 | // 28 bits 223 | type ADTSVariableHeader struct { 224 | 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) 225 | 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) 226 | AACFrameLength uint16 // 13 bits Length of the frame including headers and error_check in bytes(Table 9) 227 | 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 228 | 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) 229 | } 230 | 231 | // CopyrightIdentificationBit, 232 | // CopyrightIdentificationStart, 233 | // AACFrameLength, 一个ADTS帧的长度包括ADTS头和raw data block. 234 | // ADTSBufferFullness, 0x7FF 说明是码率可变的码流. 235 | // NumberOfRawDataBlockInFrame, 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧 236 | 237 | // 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据) 238 | func ADTSToAudioSpecificConfig(data []byte) []byte { 239 | profile := ((data[2] & 0xc0) >> 6) + 1 240 | sampleRate := (data[2] & 0x3c) >> 2 241 | channel := ((data[2] & 0x1) << 2) | ((data[3] & 0xc0) >> 6) 242 | config1 := (profile << 3) | ((sampleRate & 0xe) >> 1) 243 | config2 := ((sampleRate & 0x1) << 7) | (channel << 3) 244 | return []byte{0xAF, 0x00, config1, config2} 245 | } 246 | func AudioSpecificConfigToADTS(asc AudioSpecificConfig, rawDataLength int) (adts ADTS, adtsByte []byte, err error) { 247 | if asc.ChannelConfiguration > 8 || asc.FrameLengthFlag > 13 { 248 | err = errors.New("Reserved field.") 249 | return 250 | } 251 | 252 | // ADTSFixedHeader 253 | adts.SyncWord = 0xfff 254 | adts.ID = 0 255 | adts.Layer = 0 256 | adts.ProtectionAbsent = 1 257 | 258 | // SyncWord(12) + ID(1) + Layer(2) + ProtectionAbsent(1) 259 | adtsByte = append(adtsByte, 0xff) 260 | adtsByte = append(adtsByte, 0xf1) 261 | 262 | if asc.AudioObjectType >= 3 || asc.AudioObjectType == 0 { 263 | adts.Profile = 1 264 | } else { 265 | adts.Profile = asc.AudioObjectType - 1 266 | } 267 | 268 | adts.SamplingFrequencyIndex = asc.SamplingFrequencyIndex 269 | adts.PrivateBit = 0 270 | adts.ChannelConfiguration = asc.ChannelConfiguration 271 | adts.OriginalCopy = 0 272 | adts.Home = 0 273 | 274 | // Profile(2) + SamplingFrequencyIndex(4) + PrivateBit(1) + ChannelConfiguration(3)(取高1位) 275 | byte3 := uint8(adts.Profile<<6) + uint8(adts.SamplingFrequencyIndex<<2) + uint8(adts.PrivateBit<<1) + uint8((adts.ChannelConfiguration&0x7)>>2) 276 | adtsByte = append(adtsByte, byte3) 277 | 278 | // ADTSVariableHeader 279 | adts.CopyrightIdentificationBit = 0 280 | adts.CopyrightIdentificationStart = 0 281 | adts.AACFrameLength = 7 + uint16(rawDataLength) 282 | adts.ADTSBufferFullness = 0x7ff 283 | adts.NumberOfRawDataBlockInFrame = 0 284 | 285 | // ChannelConfiguration(3)(取低2位) + OriginalCopy(1) + Home(1) + CopyrightIdentificationBit(1) + CopyrightIdentificationStart(1) + AACFrameLength(13)(取高2位) 286 | byte4 := uint8((adts.ChannelConfiguration&0x3)<<6) + uint8((adts.AACFrameLength&0x1fff)>>11) 287 | adtsByte = append(adtsByte, byte4) 288 | 289 | // AACFrameLength(13) 290 | // xx xxxxxxxx xxx 291 | // 取中间的部分 292 | byte5 := uint8(((adts.AACFrameLength & 0x1fff) >> 3) & 0x0ff) 293 | adtsByte = append(adtsByte, byte5) 294 | 295 | // AACFrameLength(13)(取低3位) + ADTSBufferFullness(11)(取高5位) 296 | byte6 := uint8((adts.AACFrameLength&0x0007)<<5) + 0x1f 297 | adtsByte = append(adtsByte, byte6) 298 | 299 | // ADTSBufferFullness(11)(取低6位) + NumberOfRawDataBlockInFrame(2) 300 | adtsByte = append(adtsByte, 0xfc) 301 | 302 | return 303 | } 304 | -------------------------------------------------------------------------------- /codecs/avpacket.go: -------------------------------------------------------------------------------- 1 | package codecs 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var ( 8 | SendPacketPool = &sync.Pool{ 9 | New: func() interface{} { 10 | return new(SendPacket) 11 | }, 12 | } 13 | ) 14 | 15 | // Video or Audio 16 | type AVPacket struct { 17 | Timestamp uint32 18 | Type byte //8 audio,9 video 19 | IsSequence bool //序列帧 20 | IsKeyFrame bool //是否为关键帧 21 | Payload []byte 22 | Number int //编号,audio和video独立编号 23 | } 24 | 25 | func (av *AVPacket) ADTS2ASC() (tagPacket *AVPacket) { 26 | tagPacket = NewAVPacket(FLV_TAG_TYPE_AUDIO) 27 | tagPacket.Payload = ADTSToAudioSpecificConfig(av.Payload) 28 | tagPacket.IsSequence = true 29 | ADTSLength := 7 + ((1 - int(av.Payload[1]&1)) << 1) 30 | if len(av.Payload) > ADTSLength { 31 | av.Payload[0] = 0xAF 32 | av.Payload[1] = 0x01 //raw AAC 33 | copy(av.Payload[2:], av.Payload[ADTSLength:]) 34 | av.Payload = av.Payload[:(len(av.Payload) - ADTSLength + 2)] 35 | } 36 | return 37 | } 38 | 39 | func NewAVPacket(avType byte) (p *AVPacket) { 40 | p = new(AVPacket) 41 | p.Type = avType 42 | return 43 | } 44 | func (av AVPacket) Clone() *AVPacket { 45 | return &av 46 | } 47 | func (av *AVPacket) VideoFrameType() byte { 48 | return av.Payload[0] >> 4 49 | } 50 | 51 | type SendPacket struct { 52 | *AVPacket 53 | Timestamp uint32 54 | } 55 | 56 | func (packet *SendPacket) Recycle() { 57 | SendPacketPool.Put(packet) 58 | } 59 | 60 | func NewSendPacket(p *AVPacket, timestamp uint32) (result *SendPacket) { 61 | result = SendPacketPool.Get().(*SendPacket) 62 | result.AVPacket = p 63 | result.Timestamp = timestamp 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /codecs/define.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-05 17:11:56 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:12:36 6 | * @FilePath: \stream\codecs\define.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package codecs 11 | 12 | type AudioCodecID byte 13 | type VideoCodecID byte 14 | 15 | const ( 16 | ADTS_HEADER_SIZE = 7 17 | CodecID_AAC AudioCodecID = 0xA 18 | CodecID_PCMA AudioCodecID = 7 19 | CodecID_PCMU AudioCodecID = 8 20 | CodecID_H264 VideoCodecID = 7 21 | CodecID_H265 VideoCodecID = 0xC 22 | ) 23 | -------------------------------------------------------------------------------- /codecs/flv.go: -------------------------------------------------------------------------------- 1 | package codecs 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/micro-community/stream/runtime/pool" 7 | "github.com/micro-community/stream/util" 8 | ) 9 | 10 | const ( 11 | // FLV Tag Type 12 | FLV_TAG_TYPE_AUDIO = 0x08 13 | FLV_TAG_TYPE_VIDEO = 0x09 14 | FLV_TAG_TYPE_SCRIPT = 0x12 15 | ) 16 | 17 | var ( 18 | // 音频格式. 4 bit 19 | SoundFormat = map[byte]string{ 20 | 0: "Linear PCM, platform endian", 21 | 1: "ADPCM", 22 | 2: "MP3", 23 | 3: "Linear PCM, little endian", 24 | 4: "Nellymoser 16kHz mono", 25 | 5: "Nellymoser 8kHz mono", 26 | 6: "Nellymoser", 27 | 7: "G.711 A-law logarithmic PCM", 28 | 8: "G.711 mu-law logarithmic PCM", 29 | 9: "reserved", 30 | 10: "AAC", 31 | 11: "Speex", 32 | 14: "MP3 8Khz", 33 | 15: "Device-specific sound"} 34 | 35 | // 采样频率. 2 bit 36 | SoundRate = map[byte]int{ 37 | 0: 5500, 38 | 1: 11000, 39 | 2: 22000, 40 | 3: 44000} 41 | 42 | // 量化精度. 1 bit 43 | SoundSize = map[byte]string{ 44 | 0: "8Bit", 45 | 1: "16Bit"} 46 | 47 | // 音频类型. 1bit 48 | SoundType = map[byte]string{ 49 | 0: "Mono", 50 | 1: "Stereo"} 51 | 52 | // 视频帧类型. 4bit 53 | FrameType = map[byte]string{ 54 | 1: "keyframe (for AVC, a seekable frame)", 55 | 2: "inter frame (for AVC, a non-seekable frame)", 56 | 3: "disposable inter frame (H.263 only)", 57 | 4: "generated keyframe (reserved for server use only)", 58 | 5: "video info/command frame"} 59 | 60 | // 视频编码类型. 4bit 61 | CodecID = map[byte]string{ 62 | 1: "JPEG (currently unused)", 63 | 2: "Sorenson H.263", 64 | 3: "Screen video", 65 | 4: "On2 VP6", 66 | 5: "On2 VP6 with alpha channel", 67 | 6: "Screen video version 2", 68 | 7: "AVC", 69 | 12: "H265"} 70 | ) 71 | 72 | var FLVHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0, 0, 0, 9, 0, 0, 0, 0} 73 | 74 | func WriteFLVTag(w io.Writer, tag *SendPacket) (err error) { 75 | head := pool.GetSlice(11) 76 | defer pool.RecycleSlice(head) 77 | tail := pool.GetSlice(4) 78 | defer pool.RecycleSlice(tail) 79 | head[0] = tag.Type 80 | dataSize := uint32(len(tag.Payload)) 81 | util.BigEndian.PutUint32(tail, dataSize+11) 82 | util.BigEndian.PutUint24(head[1:], dataSize) 83 | util.BigEndian.PutUint24(head[4:], tag.Timestamp) 84 | util.BigEndian.PutUint32(head[7:], 0) 85 | if _, err = w.Write(head); err != nil { 86 | return 87 | } 88 | // Tag Data 89 | if _, err = w.Write(tag.Payload); err != nil { 90 | return 91 | } 92 | if _, err = w.Write(tail); err != nil { // PreviousTagSizeN(4) 93 | return 94 | } 95 | return 96 | } 97 | func ReadFLVTag(r io.Reader) (t byte, timestamp uint32, payload []byte, err error) { 98 | head := pool.GetSlice(11) 99 | defer pool.RecycleSlice(head) 100 | if _, err = io.ReadFull(r, head); err != nil { 101 | return 102 | } 103 | t = head[0] 104 | dataSize := util.BigEndian.Uint24(head[1:]) 105 | timestamp = util.BigEndian.Uint24(head[4:]) 106 | payload = make([]byte, int(dataSize)) 107 | if _, err = io.ReadFull(r, payload); err == nil { 108 | t := pool.GetSlice(4) 109 | _, err = io.ReadFull(r, t) 110 | pool.RecycleSlice(t) 111 | } 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /codecs/h264.go: -------------------------------------------------------------------------------- 1 | package codecs 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/micro-community/stream/util" 8 | ) 9 | 10 | // Start Code + NAL Unit -> NALU Header + NALU Body 11 | // RTP Packet -> NALU Header + NALU Body 12 | 13 | // NALU Body -> Slice Header + Slice data 14 | // Slice data -> flags + Macroblock layer1 + Macroblock layer2 + ... 15 | // Macroblock layer1 -> mb_type + PCM Data 16 | // Macroblock layer2 -> mb_type + Sub_mb_pred or mb_pred + Residual Data 17 | // Residual Data -> 18 | 19 | const ( 20 | // NALU Type 21 | NALU_Unspecified = 0 22 | NALU_Non_IDR_Picture = 1 23 | NALU_Data_Partition_A = 2 24 | NALU_Data_Partition_B = 3 25 | NALU_Data_Partition_C = 4 26 | NALU_IDR_Picture = 5 27 | NALU_SEI = 6 28 | NALU_SPS = 7 29 | NALU_PPS = 8 30 | NALU_Access_Unit_Delimiter = 9 31 | NALU_Sequence_End = 10 32 | NALU_Stream_End = 11 33 | NALU_Filler_Data = 12 34 | NALU_SPS_Extension = 13 35 | NALU_Prefix = 14 36 | NALU_SPS_Subset = 15 37 | NALU_DPS = 16 38 | NALU_Reserved1 = 17 39 | NALU_Reserved2 = 18 40 | NALU_Not_Auxiliary_Coded = 19 41 | NALU_Coded_Slice_Extension = 20 42 | NALU_Reserved3 = 21 43 | NALU_Reserved4 = 22 44 | NALU_Reserved5 = 23 45 | NALU_STAPA = 24 46 | NALU_FUA = 28 47 | // 24 - 31 NALU_NotReserved 48 | ) 49 | 50 | var ( 51 | NALU_AUD_BYTE = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xF0} 52 | NALU_Delimiter1 = []byte{0x00, 0x00, 0x01} 53 | NALU_Delimiter2 = []byte{0x00, 0x00, 0x00, 0x01} 54 | // 0x17 keyframe 7:AVC 55 | // 0x00 AVC sequence header 56 | // 0x00 0x00 0x00 57 | // 0x01 configurationVersion 58 | // 0x42 AVCProfileIndication 59 | // 0x00 profile_compatibility 60 | // 0x1E AVCLevelIndication 61 | // 0xFF lengthSizeMinusOne 62 | RTMP_AVC_HEAD = []byte{0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x1E, 0xFF} 63 | RTMP_KEYFRAME_HEAD = []byte{0x17, 0x01, 0x00, 0x00, 0x00} 64 | RTMP_NORMALFRAME_HEAD = []byte{0x27, 0x01, 0x00, 0x00, 0x00} 65 | ) 66 | var NALU_SEI_BYTE []byte 67 | 68 | // H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL) 69 | // NAL - Network Abstract Layer 70 | // raw byte sequence payload (RBSP) 原始字节序列载荷 71 | // SplitH264 以0x00000001分割H264裸数据 72 | func SplitH264(payload []byte) (nalus [][]byte) { 73 | for _, v := range bytes.SplitN(payload, NALU_Delimiter2, -1) { 74 | if len(v) == 0 { 75 | continue 76 | } 77 | nalus = append(nalus, bytes.SplitN(v, NALU_Delimiter1, -1)...) 78 | } 79 | return 80 | } 81 | 82 | type H264 struct { 83 | SPS []byte 84 | PPS []byte 85 | } 86 | 87 | func min(a, b int) int { 88 | if a < b { 89 | return a 90 | } 91 | return b 92 | } 93 | 94 | // Payload 分包,用于RTP传输 95 | func (h264 *H264) Payload(mtu int, payload []byte) (payloads [][]byte) { 96 | if payload == nil { 97 | return payloads 98 | } 99 | videoFrameType := payload[0] >> 4 100 | if videoFrameType == 1 || videoFrameType == 4 { 101 | payloads = append(payloads, h264.SPS, h264.PPS) 102 | } 103 | for nalu, naluLen := payload[5:], 4; len(nalu) > naluLen; naluLen = int(util.BigEndian.Uint32(nalu)) + 4 { 104 | nalu = nalu[naluLen:] 105 | naluType := nalu[0] & 0x1F //00011111 106 | naluRefIdc := nalu[0] & 0x60 //1110000 107 | 108 | if naluType == 9 || naluType == 12 { 109 | continue 110 | } 111 | 112 | // Single NALU 113 | if len(nalu) <= mtu { 114 | out := make([]byte, len(nalu)) 115 | copy(out, nalu) 116 | payloads = append(payloads, out) 117 | continue 118 | } 119 | 120 | // FU-A 121 | maxFragmentSize := mtu - 2 122 | 123 | // The FU payload consists of fragments of the payload of the fragmented 124 | // NAL unit so that if the fragmentation unit payloads of consecutive 125 | // FUs are sequentially concatenated, the payload of the fragmented NAL 126 | // unit can be reconstructed. The NAL unit type octet of the fragmented 127 | // NAL unit is not included as such in the fragmentation unit payload, 128 | // but rather the information of the NAL unit type octet of the 129 | // fragmented NAL unit is conveyed in the F and NRI fields of the FU 130 | // indicator octet of the fragmentation unit and in the type field of 131 | // the FU header. An FU payload MAY have any number of octets and MAY 132 | // be empty. 133 | 134 | naluData := nalu 135 | // According to the RFC, the first octet is skipped due to redundant information 136 | naluDataIndex := 1 137 | naluDataLength := len(nalu) - naluDataIndex 138 | naluDataRemaining := naluDataLength 139 | 140 | if min(maxFragmentSize, naluDataRemaining) <= 0 { 141 | continue 142 | } 143 | 144 | for naluDataRemaining > 0 { 145 | currentFragmentSize := min(maxFragmentSize, naluDataRemaining) 146 | out := make([]byte, 2+currentFragmentSize) 147 | 148 | // +---------------+ 149 | // |0|1|2|3|4|5|6|7| 150 | // +-+-+-+-+-+-+-+-+ 151 | // |F|NRI| Type | 152 | // +---------------+ 153 | out[0] = NALU_FUA | naluRefIdc 154 | 155 | // +---------------+ 156 | //|0|1|2|3|4|5|6|7| 157 | //+-+-+-+-+-+-+-+-+ 158 | //|S|E|R| Type | 159 | //+---------------+ 160 | 161 | out[1] = naluType 162 | if naluDataRemaining == naluDataLength { 163 | // Set start bit 164 | out[1] |= 1 << 7 165 | } else if naluDataRemaining-currentFragmentSize == 0 { 166 | // Set end bit 167 | out[1] |= 1 << 6 168 | } 169 | 170 | copy(out[2:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) 171 | payloads = append(payloads, out) 172 | 173 | naluDataRemaining -= currentFragmentSize 174 | naluDataIndex += currentFragmentSize 175 | } 176 | 177 | } 178 | return 179 | } 180 | 181 | type NALUnit struct { 182 | NALUHeader 183 | RBSP 184 | } 185 | 186 | // NALUHeader for h264 187 | type NALUHeader struct { 188 | forbidden_zero_bit byte // 1 bit 0 189 | nal_ref_idc byte // 2 bits nal_unit_type等于6,9,10,11或12的NAL单元其nal_ref_idc都应等于 0 190 | nal_uint_type byte // 5 bits 包含在 NAL 单元中的 RBSP 数据结构的类型 191 | } 192 | 193 | type RBSP interface { 194 | } 195 | 196 | /* 197 | 0 Unspecified non-VCL 198 | 1 Coded slice of a non-IDR picture VCL 199 | 2 Coded slice data partition A VCL 200 | 3 Coded slice data partition B VCL 201 | 4 Coded slice data partition C VCL 202 | 5 Coded slice of an IDR picture VCL 203 | 6 Supplemental enhancement information (SEI) non-VCL 204 | 7 Sequence parameter set non-VCL 205 | 8 Picture parameter set non-VCL 206 | 9 Access unit delimiter non-VCL 207 | 10 End of sequence non-VCL 208 | 11 End of stream non-VCL 209 | 12 Filler data non-VCL 210 | 13 Sequence parameter set extension non-VCL 211 | 14 Prefix NAL unit non-VCL 212 | 15 Subset sequence parameter set non-VCL 213 | 16 Depth parameter set non-VCL 214 | 17..18 Reserved non-VCL 215 | 19 Coded slice of an auxiliary coded picture without partitioning non-VCL 216 | 20 Coded slice extension non-VCL 217 | 21 Coded slice extension for depth view components non-VCL 218 | 22..23 Reserved non-VCL 219 | 24..31 Unspecified non-VCL 220 | 221 | 0:未规定 222 | 1:非IDR图像中不采用数据划分的片段 223 | 2:非IDR图像中A类数据划分片段 224 | 3:非IDR图像中B类数据划分片段 225 | 4:非IDR图像中C类数据划分片段 226 | 5:IDR图像的片段 227 | 6:补充增强信息(SEI) 228 | 7:序列参数集(SPS) 229 | 8:图像参数集(PPS) 230 | 9:分割符 231 | 10:序列结束符 232 | 11:流结束符 233 | 12:填充数据 234 | 13:序列参数集扩展 235 | 14:带前缀的NAL单元 236 | 15:子序列参数集 237 | 16 – 18:保留 238 | 19:不采用数据划分的辅助编码图像片段 239 | 20:编码片段扩展 240 | 21 – 23:保留 241 | 24 – 31:未规定 242 | 243 | nal_unit_type NAL类型 nal_reference_bit 244 | 0 未使用 0 245 | 1 非IDR的片 此片属于参考帧,则不等于0,不属于参考帧,则等与0 246 | 2 片数据A分区 同上 247 | 3 片数据B分区 同上 248 | 4 片数据C分区 同上 249 | 5 IDR图像的片 5 250 | 6 补充增强信息单元(SEI) 0 251 | 7 序列参数集 非0 252 | 8 图像参数集 非0 253 | 9 分界符 0 254 | 10 序列结束 0 255 | 11 码流结束 0 256 | 12 填充 0 257 | 13..23 保留 0 258 | 24..31 不保留 0 259 | */ 260 | 261 | func ReadPPS(w io.Writer) { 262 | 263 | } 264 | -------------------------------------------------------------------------------- /codecs/mpegts/mpegts.md: -------------------------------------------------------------------------------- 1 | #MPEGTS 2 | 3 | ---------- 4 | 5 | Name:苏荣 6 | Data:2016/5/27 09:03:30 7 | 8 | 9 | ---------- 10 | 11 | ## PSI(Program Specific Information) 节目特定信息 12 | PSI 可以认为属于 6 个表: 13 | 1) 节目相关表(PAT) 14 | 2) TS 节目映射表(PMT) 15 | 3) 网络信息表(NIT) 16 | 4) 有条件访问表(CAT) 17 | 5) 传输流描述表 18 | 6) IPMP 控制信息表 19 | 20 | ##ES流(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流. 21 | 22 | ##PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流 23 | 24 | ##PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p). 25 | 26 | ##TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输). 27 | 28 | ##PES ES TS 29 | 视频压缩成H264码流,可以称之为ES流,将其每帧打包为PES流,然后分拆为多个188字节,称为TS流. 30 | 31 | H264(ES) = PES1(一帧ES打包) + PES2(一帧ES打包) + PES3(一帧ES打包) + ... 32 | 33 | PES1 = PES1 Header + PES1 Payload = PES1 Packet Start Code Prefix + Stream ID + PES1 Packet Length + Send PES1 Header(不确定大小) + PES1 Payload 34 | 35 | PES1 Payload = TS1 Payload + TS2 Payload + TS3 Payload + ... 36 | 37 | PES1 = TS1 + TS2 + TS3 + .... 38 | 39 | PES1 = TS1(TS1 Header + PES1 Header + TS1 Payload) + TS2(有三种可能) + TS3(有三种可能) + ...... 40 | 41 | TS1(TS流第一个包) = TS1 Header + PES1 Header + TS1 Payload 42 | 43 | TS2(TS流第二个包,第一种情况) = TS2 Header + 自适应字段 + TS2 Payload (出现概率 1%) 44 | 45 | TS2(TS流第二个包,第二种情况) = TS2 Header + 自适应字段 (出现概率 0.1%) 46 | 47 | TS2(TS流第二个包,第三种情况) = TS2 Header + TS2 Payload (出现概率 98.9%) 48 | 49 | 一段ES流 = N个PES(N帧) 50 | 51 | 同一个PES的TS的PID是相同的 52 | 53 | ##寻找第一个TS包 54 | Header PID = 0x000 说明数据包是PAT表信息 55 | 第一个TS包 一般叫做 PAT (Program Association Table,节目相关表) 56 | 57 | TS流 : PID=005 + PID=002 + PID=000 58 | 59 | 一般来说第一个TS包一般在第一个位置,本例举出一个特殊情况(第一个TS包在第三) 60 | 61 | 在寻找第一个TS包时,不断读取TS包,直到找到pid=000的位置,并将读取过的TS包置入缓冲区 62 | 63 | ##寻找下一个TS包 64 | 第二个TS包 一般叫做PMT(Program Map Table,节目映射表) 65 | 66 | ##解析TS包 67 | payload_unit_start_indicator : 该字段用来表示TS包的有效净荷有PES包或者PSI数据的情况. 68 | 69 | 当TS包带有PES包数据时(出现概率99.9%).不带PES包(出现概率0.1%). 70 | 71 | 1. 当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点: 72 | a. 置为1,标识TS包的有效净荷以PES包的第一个字节开始. 73 | b. 置为0,表示TS包的开始不是PES包. 74 | 75 | 2. 当TS包带有PSI数据时,payload_unit_start_indicator具有以下特点: 76 | a. 置为1,表示TS包带有PSI部分的第一个字节,即第一个字节带有指针pointer_field. 77 | b. 置为0,表示TS包不带有一个PSI部分的第一个字节,即在有效净荷中没有指针point_field. 78 | c. 对于空包的包,payload_unit_start_indicator应该置为0 79 | 80 | adaptionFieldControl: 81 | 01 -> 仅含有效负载(TS包第三种情况) 82 | 10 -> 仅含调整字段(TS包第二种情况) 83 | 11 -> 含有调整字段和有效负载(TS包第一种情况) 84 | 85 | TS流,通过一个个的TS包来传送. TS包可以是传送PSI SI等各表的数据包,也可以是传送节目音视频数据(携带的PES包:音视频基本流包)的包;TS携带 PSI SI等表的数据时,各个表以各表对应的Section语法格式做为传输单元存放到TS包中 以便传输; 86 | TS包,有一个TS包的PID,系统就是根据这个PID来找对应的TS包;对于包含音视频数据(PES包)的TS包,系统通过TS的PID找到对应TS数据包,提取其中的数据组合成节目的音视频;对于携带PSI SI等数据的TS包,系统通过TS的PID找到对应TS数据包,提取各个PSI SI数据表格,用来指导系统;因此其中部分PID用来固定传输某些数据内容. 87 | 88 | 有了TS的PID后, 如果TS包携带的是PSI SI等表格的Section数据时,有时还不能确定该PID的TS包中携带的数据是什么,SDT BAT ST 等表传送时,都用的是PID为0X0011的TS数据包,对于这种携带PSI SI Section单元的TS包,对应的数据(表的Section语法中)还有一个 TABLE_ID字段,用来可以确定是具体的什么表 89 | 90 | 因此PID+TableID就可以确定负载带了什么,是PES还是PSI. 91 | 92 | 93 | ---------- 94 | 95 | 96 | 1. 第一个包: 97 | 98 | 包头 : 47 60 00 10 99 | 0x47 : syncByte 100 | 0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. 101 | 0x000 : 0 0000 0000 0000, pid = 0,说明是第一个TS包(PAT表) 102 | 0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) 103 | 104 | 负载 : 00 00 B0 0D 00 00 C1 00 00 00 01 E0 105 | 81 0C 8C BE 32 FF FF......FF 106 | 107 | 指针 : 00 108 | table id : 00 109 | 固定值 : B (1011) 110 | section_length : 0 0D(值:13) 111 | transport_stream_id : 00 00 112 | version number & current_next_indicator : C1 113 | section_number : 00 114 | last_section_number : 00 115 | program_number : 00 01 116 | program_map_PID : E0 81(因为program_number > 0) 117 | CRC_32 : 0C 8C BE 32 118 | 119 | if (program_number == 0) 120 | { 121 | network_PID 122 | }else 123 | { 124 | program_map_PID 125 | } 126 | 127 | E0 81 = reserved3 + program_map_PID = | 1110 0000 | 1000 0001 | 128 | program_map_PID = 0x81(说明PMT的pid为081) 129 | 130 | 131 | ---------- 132 | 133 | 134 | 2. 第二个包 135 | 136 | 包头 : 47 60 81 10 137 | 0x47 : syncByte 138 | 0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. 139 | 0x081 : 0 0000 1000 0001, pid = 0x081(说明是PMT表,因为前面的PAT表给出了) 140 | 0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) 141 | 142 | 负载 : 00 02 B0 17 00 01 C1 00 00 E8 10 F0 00 1B E8 10 143 | F0 00 03 E8 14 F0 00 66 74 A4 2D FF FF FF FF FF......FF 144 | 145 | 指针 : 00 146 | table id : 02 147 | 固定值 : B 148 | section_length : 0 17(值:23,表示到后面FF FF FF FF FF FF之前总共有23个字节) 149 | program_number : 00 01 150 | reserved2 & version_number & current_next_indicator : C1 151 | section_number : 00 152 | last_section_number : 00 153 | PCR_PID : E8 10 154 | program_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 155 | 156 | 第一流分析 : 1B E8 10 F0 00 157 | stream_type : 1B 视频流(H264)(ITU-T H.264建议书| SO/IEC 14496-10 视频中定义的 AVC 视频流) 158 | elementary_PID : E8 10 前3位为保留位取后13位 则PID=810 表示此PID的都是视频流 159 | ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 160 | 161 | 第二流分析 : 03 E8 14 F0 00 162 | stream_type : 03 音频流(MP3) 163 | elementary_PID : E8 14 前3位为保留位取后13位 则PID=814 表示此PID的都是音频流 164 | ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 165 | 166 | 167 | 168 | CRC : 66 74 A4 2D 169 | 170 | 171 | reserved4 + program_info_length = | 1111 0000 | 0000 0000 | 172 | program_info_length = 0 173 | 174 | stream_type : 03 表示流是音频流 MP3 格式 814 表示 pid=814 的TS包存储的是MP3格式的音频流. 175 | stream_type : 01 表示流是视频流 h264格式 810 表示 pid=810 的TS包存储的是h264格式的视频流 176 | 177 | 178 | ---------- 179 | 180 | 181 | 3. 第三个包 182 | 包头 : 47 48 14 10 183 | 0x47 : syncByte 184 | 0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. 185 | 0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) 186 | 0x10 : 0001 0000, adaptionFieldControl = 01 187 | 188 | 这里: 189 | payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 190 | adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) 191 | 192 | 负载 : 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 193 | 194 | packetStartCodePrefix : 00 00 01 195 | streamID : C0 196 | pes_PacketLength : 01 88(值为392,占用392个字节,一帧数据长度,也可以置为0) 197 | Sned PES HEADER : 占用不确定位 本例为:80 80 05 21 00 01 96 07 198 | 199 | 200 | Sned PES HEADER 包括以下几个字段: 80 80 05 21 00 01 96 07(解析为二进制显示) 201 | | 8 0 | 8 0 | 0 5 | 2 1 | 0 0 | 0 1 | 9 6 | 0 7 | 202 | | 1000 0000| 1000 0000 | 0000 0101 | 0010 0001 | 0000 0000 | 0000 0001 | 1001 0110 | 0000 1110 | 203 | 204 | (注意,下面的数值是用二进制表示,不特别声明,都是用16进制表示) 205 | (0x80) 206 | constTen : 10 固定 207 | PES_scrambling_control : 00 PES加扰控制 208 | PES_priority : 0 PES 包中该有效载荷的优先级 209 | data_alignment_indicator : 0 数据定位指示符 210 | copyright : 0 PES 包有效载荷的素材依靠版权所保护 211 | original_or_copy : 0 PES 包有效载荷的内容是原始的 212 | 213 | (0x80) 214 | PTS_DTS_flags : 10 PES 包头中 PTS 字段存在 215 | ESCR_flag : 0 216 | ES_rate_flag : 0 217 | DSM_trick_mode_flag : 0 218 | additional_copy_info_flag : 0 219 | PES_CRC_flag : 0 220 | PES_extension_flag : 0 221 | 222 | (0x05) 223 | PES_header_data_length : 0000 0101(值为5)PES头数据长度,表示后面还有5个字节,之后就是一帧的数据 224 | 225 | (0x4200032C)(十进制:1107297068) 226 | PTS(presentation time stamp): 0010 0001 0000 0000 0000 0001 1001 0110 0 227 | 228 | 下面字段在本例中都没有: 229 | ESCR(42) = ESCR_base(33) + ESCR_extension(9) 230 | ES_rate(22) 231 | DSM特技方式(8) 232 | additional_copy_info(7) 233 | previous_PES_packet_CRC(16) 234 | PES_Extension(不确定) 235 | 236 | 237 | 因为 PTS_DTS_flags == 10,所以本例中只有PTS没有DTS. 238 | 239 | 240 | 注意 : 本TS包 包含PES头信息 说明开始下一帧 241 | 242 | ---------- 243 | 244 | 245 | 4. 第四个包 246 | 包头 : 47 08 14 11 247 | 0x47 : syncByte 248 | 0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. 249 | 0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) 250 | 0x11 : 0001 0001, adaptionFieldControl = 01 251 | 252 | 这里: 253 | payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 254 | adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) 255 | 256 | ---------- 257 | 258 | 259 | 5. 第五个包 260 | 包头 : 47 08 14 32 261 | 0x47 : syncByte 262 | 0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. 263 | 0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) 264 | 0x32 : 0011 0010, adaptionFieldControl = 11 265 | 266 | 这里: 267 | payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 268 | adaptionFieldControl = 11, 说明先有自适应字段,再有有效载荷(TS包第一种情况) 269 | 270 | 负载 : 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 271 | 272 | adaptation_field_length : 99(值为153,表示占用153个字节) 273 | 274 | discontinuity_indicator & random_access_indicator & 275 | elementary_stream_priority_indicator & PCR_flag & 276 | OPCR_flag & splicing_point_flag & 277 | transport_private_data_flag & adaptation_field_extension_flag : 00 剩下的所有字段都为0 278 | 279 | (00 FF FF FF ... FF)这里都是调整字段,从52 DE E6 B5 D0(从00(FF之前,99之后) 开始算是第1个字节,跳到第153个字节)开始,就是真正的帧数据了 280 | 281 | 282 | ---------- 283 | 284 | 285 | 6. 第六个包 286 | 包头 : 47 48 14 13 287 | 0x47 : syncByte 288 | 0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. 289 | 0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) 290 | 0x13 : 0001 0011, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) 291 | 292 | 这里: 293 | payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 294 | adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) 295 | 296 | 负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 A6 E7 FF FD 297 | 298 | packetStartCodePrefix : 00 00 01 299 | streamID : C0 300 | pes_PacketLength : 01 88(值为392,占用392个字节) 301 | Sned PES HEADER : 占用不确定位 302 | 303 | 所以本包数据流ID 和 第二个包的流ID是一样的 304 | 305 | 注意 : 本TS包 又包含PES头信息 说明开始下一帧 306 | 307 | 308 | ---------- 309 | 310 | 7. 第七个包 311 | 包头 : 47 48 10 30 312 | 0x47 : syncByte 313 | 0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. 314 | 0x810 : 0 1000 0001 0000, pid = 0x810(视频H264) 315 | 0x30 : 0011 0000, adaptionFieldControl = 11,说明含有调整字段和有效负载(TS包第一种情况) 316 | 317 | 这里: 318 | payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 319 | adaptionFieldControl = 11, 说明含有调整字段和有效负载(TS包第一种情况) 320 | 321 | 负载 : 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 322 | 323 | adaptation_field_length : 07(值为7,表示占用153个字节) 324 | 325 | discontinuity_indicator & random_access_indicator & 326 | elementary_stream_priority_indicator & PCR_flag & 327 | OPCR_flag & splicing_point_flag & 328 | transport_private_data_flag & adaptation_field_extension_flag : 10 329 | 330 | (10 00 00 01 0F 7E 88)调整字段 331 | 332 | packetStartCodePrefix : 00 00 01 333 | streamID : EO 334 | pes_PacketLength : 00 00(值为0,占用0个字节,一帧数据长度,也可以置为0,此时需要自己去计算) 335 | Sned PES HEADER : 占用不确定位 336 | 337 | 338 | ---------- 339 | 340 | 341 | 8. 第八个包 342 | 包头 : 47 08 10 11 343 | 0x47 : syncByte 344 | 0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. 345 | 0x810 : 0 1000 0001 0000, pid = 0x810(视频H264) 346 | 0x11 : 0001 0001, adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) 347 | 348 | 这里: 349 | payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 350 | adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) 351 | 352 | 353 | ---------- 354 | 355 | 总结这个八个包: 356 | 357 | 第一个TS包(PID:0X00) : 包含了PAT. 358 | 第二个TS包(PID:0X81) : 包含了PMT. 359 | 第三个TS包(PID:0x814) : 音频PES包头所有的TS包. 360 | 第四个TS包(PID:0x814) : 音频TS包. 361 | 第五个TS包(PID:0x814) : 音频TS包. 362 | 第六个TS包(PID:0x814) : 音频PES包头所有的TS包. 363 | 第七个TS包(PID:0x810) : 视频PES包头所有的TS包. 364 | 第八个TS包(PID:0x810) : 视频TS包. 365 | 366 | 367 | ---------- 368 | 369 | 370 | // Packet Header: 371 | // PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的.如果一个TS流中的一个Packet的Packet Header中的PID是0x0000, 372 | // 那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video,Audio或其他业务信息). 373 | 374 | // 分析一个Header: 375 | // 二进制: 0100 0111 0000 0111 1110 0101 0001 0010 376 | // 十六进制: 4 7 0 7 e 5 1 2 377 | 378 | // syncByte = 0x47 就是0x47,这是DVB TS规定的同步字节,固定是0x47 379 | // transportErrorIndicator = 0 表示当前包没有发生传输错误 380 | // payloadUnitStartIndicator = 0 具体含义参考ISO13818-1标准文档 381 | // transportPriority = 0 表示当前包是低优先级 382 | // pid = 0x07e5(0 0111 1110 0101) Video PID 383 | // transportScramblingControl = 00 表示节目没有加密 384 | // adaptionFieldControl = 01 具体含义参考ISO13818-1标准文档 385 | // continuityCounter = 0010 表示当前传送的相同类型的包是第3个 386 | 387 | 388 | ---------- 389 | 390 | 391 | // 分析一段TS流:(PAT) 392 | // Packet Header : 0x47 0x40 0x00 0x10 393 | // 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 394 | 395 | // Header PID = 0x0000 说明数据包是PAT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1") 396 | // 所以,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 397 | 398 | // 399 | // 00 | b0 11 | 00 01 | c1 | 00 | 00 | 00 00 | e0 1f | 00 01 e1 00 | 400 | // 401 | 402 | // table_id = 0000 0000 403 | 404 | // section_syntax_indicator = 1 405 | // zero = 0 406 | // reserved1 = 11 407 | // sectionLength = 0000 0001 0001 408 | 409 | // transportStreamID = 0000 0000 0000 0001 410 | 411 | // reserved2 = 11 412 | // versionNumber = 0000 0 413 | // currentNextIndicator 1 414 | 415 | // sectionNumber = 0000 0000 416 | 417 | // lastSectionNumber = 0000 0000 418 | 419 | // programNumber = 0000 0000 0000 0000 420 | 421 | // reserved3 = 111 422 | // networkPID = 0 0000 0001 1111 423 | 424 | // crc32 425 | 426 | 427 | ---------- 428 | 429 | 430 | // 分析一段TS流:(PMT) 431 | // Packet Header : 0x47 0x43 0xe8 0x12 432 | // 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 433 | 434 | // Header PID = 0x03e8 说明数据包是PMT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1") 435 | // 所以,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 436 | 437 | // 1 2 3 4 5 6 7 8 9 10 11 12 438 | // 02 | b0 12 | 00 01 | c1 | 00 | 00 | e3 e9 | f0 00 | 1b | e3 e9 | f0 00 | f0 af b4 4f | 439 | // 440 | 441 | // 1: 442 | // table_id = 0000 0010 443 | 444 | // 2: 445 | // section_syntax_indicator = 1 446 | // zero = 0 447 | // reserved1 = 11 448 | // section_length = 0000 0001 0010 449 | 450 | // 3: 451 | // program_number = 0000 0000 0000 0001 452 | 453 | // 4: 454 | // reserved2 = 11 455 | // version_number = 00 000 456 | // current_next_indicator = 1 457 | 458 | // 5: 459 | // section_number = 0000 0000 460 | 461 | // 6: 462 | // last_section_number = 0000 0000 463 | 464 | // 7: 465 | // reserved3 = 111 466 | // PCR_PID = 0 0011 1110 1001 467 | 468 | // 8: 469 | // reserved4 = 1111 470 | // program_info_length = 0000 0000 0000 471 | 472 | // 9: 473 | // stream_type = 0001 1011 474 | 475 | // 10: 476 | // reserved5 = 111 477 | // elementary_PID = 0 0011 1110 1001 478 | 479 | // 11: 480 | // reserved6 = 1111 481 | // ES_info_length = 0000 0000 0000 482 | 483 | // 12: 484 | // crc 485 | 486 | 487 | ---------- 488 | 489 | 490 | ##TS流解码过程 491 | 1. 获取TS中的PAT 492 | 2. 获取TS中的PMT 493 | 3. 根据PMT可以知道当前网络中传输的视频(音频)类型(H264),相应的PID,PCR的PID等信息. 494 | 4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等. 495 | 5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包. 因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包. 496 | 6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES. 497 | 7. 直接将 被被拔掉 PES包头的ES包送给decoder就可以进行解码.解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步. 498 | 8. I,B,B,P 信息是在ES中的. 499 | 500 | 501 | ---------- 502 | 503 | 504 | 1. 首先找到PID为0x00的TS包,找到里面的节目映射表(PMT)PID,因为可能有几个节目信息.所以可能有几个PMT_PID,以一个为例 505 | 2. 接着查找该PMT_PID的TS包,通常就紧接着.在该PMT包中找音频和视频的PID.以视频为例. 506 | 3. 开始提取一帧ES数据 507 | 3.1 查找视频PID的TS包 508 | 3.2 找PES包头,方法:TS包头第2个字节的高6位(有效载荷单元起始指示符)为1的TS包,跳过自适应字段,找到PES包头,提取时间戳,再跳至ES数据,这就是一帧ES数据的开始部分. 509 | 3.3 查找有效载荷单元起始指示符为0的TS包.跳过TS包头,跳过自适应字段,提取后面的ES数据 510 | 3.4 同3.3接着查找 511 | 3.5 当碰到有效载荷单元起始指示符又变为1的视频TS包,就知道这是下一帧的开始了,将前面的所有ES数据组合成一帧数据.开始下一轮组帧. 512 | 513 | 514 | ---------- 515 | 516 | 517 | ##参考文档: 518 | 519 | 1. [TS流](http://blog.csdn.net/cabbage2008/article/category/5885203) 520 | 1. [TS各个表 与 SECTION 的解析 CAS原理 ](http://blog.sina.com.cn/s/blog_6b94d5680101r5l6.html) -------------------------------------------------------------------------------- /codecs/mpegts/mpegts_crc32.go: -------------------------------------------------------------------------------- 1 | package mpegts 2 | 3 | // http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c 4 | 5 | //Crc32TableTSPackage for crc32 computing 6 | var Crc32TableTSPackage = []uint32{ 7 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 8 | 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 9 | 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 10 | 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 11 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 12 | 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 13 | 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 14 | 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 15 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 16 | 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 17 | 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 18 | 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 19 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 20 | 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 21 | 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 22 | 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 23 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 24 | 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 25 | 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 26 | 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 27 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 28 | 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 29 | 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 30 | 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 31 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 32 | 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 33 | 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 34 | 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 35 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 36 | 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 37 | 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 38 | 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 39 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 40 | 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 41 | 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 42 | 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 43 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 44 | 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 45 | 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 46 | 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 47 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 48 | 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 49 | 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, 50 | } 51 | 52 | //GetCRC32 return CRC32 for byte array 53 | func GetCRC32(data []byte) (crc uint32) { 54 | crc = 0xffffffff 55 | 56 | for _, v := range data { 57 | crc = (crc << 8) ^ Crc32TableTSPackage[((crc>>24)^uint32(v))&0xff] 58 | 59 | } 60 | 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /codecs/mpegts/mpegts_pat.go: -------------------------------------------------------------------------------- 1 | package mpegts 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/micro-community/stream/util" 10 | ) 11 | 12 | // ios13818-1-CN.pdf 43(57)/166 13 | // 14 | // PAT 15 | // 16 | 17 | var DefaultPATPacket = []byte{ 18 | // TS Header 19 | 0x47, 0x40, 0x00, 0x10, 20 | 21 | // Pointer Field 22 | 0x00, 23 | 24 | // PSI 25 | 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 26 | 27 | // PAT 28 | 0x00, 0x01, 0xe1, 0x00, 29 | 30 | // CRC 31 | 0xe8, 0xf9, 0x5e, 0x7d, 32 | 33 | // Stuffing 167 bytes 34 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 35 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 36 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 37 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 38 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 39 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 41 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 42 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 43 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 44 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 45 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 46 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 47 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 48 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 49 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 50 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 51 | } 52 | 53 | // TS Header : 54 | // SyncByte = 0x47 55 | // TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0), 56 | // Pid = 0, 57 | // TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000), 58 | 59 | // PSI : 60 | // TableID = 0x00, 61 | // SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11), 62 | // SectionLength = 13(0x00d) 63 | // TransportStreamID = 0x0001 64 | // Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0), 65 | // SectionNumber = 0x00 66 | // LastSectionNumber = 0x00 67 | 68 | // PAT : 69 | // ProgramNumber = 0x0001 70 | // Reserved3 = 15(B:1110), ProgramMapPID = 4097(0x1001) 71 | 72 | // PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来,以后会使用到这些数据 73 | type MpegTsPATProgram struct { 74 | ProgramNumber uint16 // 16 bit 节目号 75 | Reserved3 byte // 3 bits 保留位 76 | NetworkPID uint16 // 13 bits 网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID 77 | ProgramMapPID uint16 // 13 bit 节目映射表的PID,节目号大于0时对应的PID.每个节目对应一个 78 | } 79 | 80 | // Program Association Table (节目关联表) 81 | // 节目号为0x0000时,表示这是NIT,PID=0x001f,即3. 82 | // 节目号为0x0001时,表示这是PMT,PID=0x100,即256 83 | type MpegTsPAT struct { 84 | // PSI 85 | TableID byte // 8 bits 0x00->PAT,0x02->PMT 86 | SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1 87 | Zero byte // 1 bit 0 88 | Reserved1 byte // 2 bits 保留位 89 | SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD) 90 | TransportStreamID uint16 // 16 bits 该字段充当标签,标识网络内此传输流有别于任何其他多路复用流.其值由用户规定 91 | Reserved2 byte // 2 bits 保留位 92 | VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号 93 | CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效,0则要等待下一个表 94 | SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段 95 | LastSectionNumber byte // 8 bits 最后一个分段的号码 96 | 97 | // N Loop 98 | Program []MpegTsPATProgram // PAT表里面的所有频道索引信息 99 | 100 | Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值 101 | } 102 | 103 | func ReadPAT(r io.Reader) (pat MpegTsPAT, err error) { 104 | lr, psi, err := ReadPSI(r, PSI_TYPE_PAT) 105 | if err != nil { 106 | return 107 | } 108 | 109 | pat = psi.Pat 110 | 111 | // N Loop 112 | // 一直循环去读4个字节,用lr的原因是确保不会读过头了. 113 | for lr.N > 0 { 114 | 115 | // 获取每一个频道的节目信息,保存起来 116 | programs := MpegTsPATProgram{} 117 | 118 | programs.ProgramNumber, err = util.ReadByteToUint16(lr, true) 119 | if err != nil { 120 | return 121 | } 122 | 123 | // 如果programNumber为0,则是NetworkPID,否则是ProgramMapPID(13) 124 | if programs.ProgramNumber == 0 { 125 | programs.NetworkPID, err = util.ReadByteToUint16(lr, true) 126 | if err != nil { 127 | return 128 | } 129 | 130 | programs.NetworkPID = programs.NetworkPID & 0x1fff 131 | } else { 132 | programs.ProgramMapPID, err = util.ReadByteToUint16(lr, true) 133 | if err != nil { 134 | return 135 | } 136 | 137 | programs.ProgramMapPID = programs.ProgramMapPID & 0x1fff 138 | } 139 | 140 | pat.Program = append(pat.Program, programs) 141 | } 142 | if cr, ok := r.(*util.Crc32Reader); ok { 143 | err = cr.ReadCrc32UIntAndCheck() 144 | if err != nil { 145 | return 146 | } 147 | } 148 | 149 | return 150 | } 151 | 152 | func WritePAT(w io.Writer, pat MpegTsPAT) (err error) { 153 | bw := &bytes.Buffer{} 154 | 155 | // 将pat(所有的节目索引信息)写入到缓冲区中 156 | for _, pats := range pat.Program { 157 | if err = util.WriteUint16ToByte(bw, pats.ProgramNumber, true); err != nil { 158 | return 159 | } 160 | 161 | if pats.ProgramNumber == 0 { 162 | if err = util.WriteUint16ToByte(bw, pats.NetworkPID&0x1fff|7<<13, true); err != nil { 163 | return 164 | } 165 | } else { 166 | // | 0001 1111 | 1111 1111 | 167 | // 7 << 13 -> 1110 0000 0000 0000 168 | if err = util.WriteUint16ToByte(bw, pats.ProgramMapPID&0x1fff|7<<13, true); err != nil { 169 | return 170 | } 171 | } 172 | } 173 | 174 | if pat.SectionLength == 0 { 175 | pat.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes())) 176 | } 177 | 178 | psi := MpegTsPSI{} 179 | 180 | psi.Pat = pat 181 | 182 | if err = WritePSI(w, PSI_TYPE_PAT, psi, bw.Bytes()); err != nil { 183 | return 184 | } 185 | 186 | return 187 | } 188 | 189 | func WritePATPacket(w io.Writer, tsHeader []byte, pat MpegTsPAT) (err error) { 190 | if pat.TableID != TABLE_PAS { 191 | err = errors.New("PAT table ID error") 192 | return 193 | } 194 | 195 | // 将所有要写的数据(PAT),全部放入到buffer中去. 196 | // buffer 里面已经写好了整个pat表(PointerField+PSI+PAT+CRC) 197 | bw := &bytes.Buffer{} 198 | if err = WritePAT(bw, pat); err != nil { 199 | return 200 | } 201 | 202 | // TODO:如果Pat.Program里面包含的信息很大,大于188? 203 | stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len()) 204 | 205 | // PATPacket = TsHeader + PAT + Stuffing Bytes 206 | var PATPacket []byte 207 | PATPacket = append(PATPacket, tsHeader...) 208 | PATPacket = append(PATPacket, bw.Bytes()...) 209 | PATPacket = append(PATPacket, stuffingBytes...) 210 | 211 | fmt.Println("-------------------------") 212 | fmt.Println("Write PAT :", PATPacket) 213 | fmt.Println("-------------------------") 214 | 215 | // 写PAT负载 216 | if _, err = w.Write(PATPacket); err != nil { 217 | return 218 | } 219 | 220 | return 221 | } 222 | 223 | func WriteDefaultPATPacket(w io.Writer) (err error) { 224 | _, err = w.Write(DefaultPATPacket) 225 | if err != nil { 226 | return 227 | } 228 | 229 | return 230 | } 231 | -------------------------------------------------------------------------------- /codecs/mpegts/mpegts_pmt.go: -------------------------------------------------------------------------------- 1 | package mpegts 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/micro-community/stream/util" 10 | ) 11 | 12 | // ios13818-1-CN.pdf 46(60)-153(167)/page 13 | // 14 | // PMT 15 | // 16 | 17 | var DefaultPMTPacket = []byte{ 18 | // TS Header 19 | 0x47, 0x41, 0x00, 0x10, 20 | 21 | // Pointer Field 22 | 0x00, 23 | 24 | // PSI 25 | 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, 26 | 27 | // PMT 28 | 0xe1, 0x01, 29 | 0xf0, 0x00, 30 | 31 | // H264 32 | 0x1b, 0xe1, 0x01, 0xf0, 0x00, 33 | 34 | // AAC 35 | 0x0f, 0xe1, 0x02, 0xf0, 0x00, 36 | 37 | //0x00, 0x00, 0x00, 0x00, 0x00, 38 | 39 | // CRC for not audio 40 | //0x00, 0x00, 0x00, 0x00, 41 | 42 | // CRC for AAC 43 | 0x9e, 0x28, 0xc6, 0xdd, 44 | 45 | // CRC for MP3 46 | // 0x4e, 0x59, 0x3d, 0x1e, 47 | 48 | // Stuffing 157 bytes 49 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 50 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 51 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 52 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 53 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 54 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 55 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 56 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 57 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 58 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 59 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 60 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 61 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 62 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 63 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 64 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 65 | } 66 | 67 | // TS Header : 68 | // SyncByte = 0x47 69 | // TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0), 70 | // Pid = 4097(0x1001), 71 | // TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000), 72 | 73 | // PSI : 74 | // TableID = 0x02, 75 | // SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11), 76 | // SectionLength = 23(0x17) 77 | // ProgramNumber = 0x0001 78 | // Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0), 79 | // SectionNumber = 0x00 80 | // LastSectionNumber = 0x00 81 | 82 | // PMT: 83 | // Reserved3 = 15(B:1110), PcrPID = 256(0x100) 84 | // Reserved4 = 16(B:1111), ProgramInfoLength = 0(0x000) 85 | // H264: 86 | // StreamType = 0x1b, 87 | // Reserved5 = 15(B:1110), ElementaryPID = 256(0x100) 88 | // Reserved6 = 16(B:1111), EsInfoLength = 0(0x000) 89 | // AAC: 90 | // StreamType = 0x0f, 91 | // Reserved5 = 15(B:1110), ElementaryPID = 257(0x101) 92 | // Reserved6 = 16(B:1111), EsInfoLength = 0(0x000) 93 | 94 | type MpegTsPmtStream struct { 95 | StreamType byte // 8 bits 指示具有 PID值的包内承载的节目元类型,其 PID值由 elementary_PID所指定 96 | Reserved5 byte // 3 bits 保留位 97 | ElementaryPID uint16 // 13 bits 指定承载相关节目元的传输流包的 PID 98 | Reserved6 byte // 4 bits 保留位 99 | EsInfoLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10比特指示紧随 ES_info_length字段的相关节目元描述符的字节数 100 | 101 | // N Loop Descriptors 102 | Descriptor []MpegTsDescriptor // 不确定字节数,可变 103 | } 104 | 105 | // Program Map Table (节目映射表) 106 | type MpegTsPMT struct { 107 | // PSI 108 | TableID byte // 8 bits 0x00->PAT,0x02->PMT 109 | SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1 110 | Zero byte // 1 bit 0 111 | Reserved1 byte // 2 bits 保留位 112 | SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD) 113 | ProgramNumber uint16 // 16 bits 指定 program_map_PID 所适用的节目 114 | Reserved2 byte // 2 bits 保留位 115 | VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号 116 | CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效 117 | SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段 118 | LastSectionNumber byte // 8 bits 最后一个分段的号码 119 | 120 | Reserved3 byte // 3 bits 保留位 0x07 121 | PcrPID uint16 // 13 bits 指明TS包的PID值.该TS包含有PCR域,该PCR值对应于由节目号指定的对应节目.如果对于私有数据流的节目定义与PCR无关.这个域的值将为0x1FFF 122 | Reserved4 byte // 4 bits 预留位 0x0F 123 | ProgramInfoLength uint16 // 12 bits 前两位bit为00.该域指出跟随其后对节目信息的描述的byte数 124 | ProgramInfoDescriptor []MpegTsDescriptor // N Loop Descriptors 可变 节目信息描述 125 | 126 | // N Loop 127 | Stream []MpegTsPmtStream // PMT表里面的所有音视频索引信息 128 | 129 | Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值 130 | } 131 | 132 | func ReadPMT(r io.Reader) (pmt MpegTsPMT, err error) { 133 | lr, psi, err := ReadPSI(r, PSI_TYPE_PMT) 134 | if err != nil { 135 | return 136 | } 137 | 138 | pmt = psi.Pmt 139 | 140 | // reserved3(3) + pcrPID(13) 141 | pcrPID, err := util.ReadByteToUint16(lr, true) 142 | if err != nil { 143 | return 144 | } 145 | 146 | pmt.PcrPID = pcrPID & 0x1fff 147 | 148 | // reserved4(4) + programInfoLength(12) 149 | // programInfoLength(12) == 0x00(固定为0) + programInfoLength(10) 150 | programInfoLength, err := util.ReadByteToUint16(lr, true) 151 | if err != nil { 152 | return 153 | } 154 | 155 | pmt.ProgramInfoLength = programInfoLength & 0x3ff 156 | 157 | // 如果length>0那么,紧跟programInfoLength后面就有length个字节 158 | if pmt.ProgramInfoLength > 0 { 159 | lr := &io.LimitedReader{R: lr, N: int64(pmt.ProgramInfoLength)} 160 | pmt.ProgramInfoDescriptor, err = ReadPMTDescriptor(lr) 161 | if err != nil { 162 | return 163 | } 164 | } 165 | 166 | // N Loop 167 | // 开始N循环,读取所有的流的信息 168 | for lr.N > 0 { 169 | var streams MpegTsPmtStream 170 | // streamType(8) 171 | streams.StreamType, err = util.ReadByteToUint8(lr) 172 | if err != nil { 173 | return 174 | } 175 | 176 | // reserved5(3) + elementaryPID(13) 177 | streams.ElementaryPID, err = util.ReadByteToUint16(lr, true) 178 | if err != nil { 179 | return 180 | } 181 | 182 | streams.ElementaryPID = streams.ElementaryPID & 0x1fff 183 | 184 | // reserved6(4) + esInfoLength(12) 185 | // esInfoLength(12) == 0x00(固定为0) + esInfoLength(10) 186 | streams.EsInfoLength, err = util.ReadByteToUint16(lr, true) 187 | if err != nil { 188 | return 189 | } 190 | 191 | streams.EsInfoLength = streams.EsInfoLength & 0x3ff 192 | 193 | // 如果length>0那么,紧跟esInfoLength后面就有length个字节 194 | if streams.EsInfoLength > 0 { 195 | lr := &io.LimitedReader{R: lr, N: int64(streams.EsInfoLength)} 196 | streams.Descriptor, err = ReadPMTDescriptor(lr) 197 | if err != nil { 198 | return 199 | } 200 | } 201 | 202 | // 每读取一个流的信息(音频流或者视频流或者其他),都保存起来 203 | pmt.Stream = append(pmt.Stream, streams) 204 | } 205 | if cr, ok := r.(*util.Crc32Reader); ok { 206 | err = cr.ReadCrc32UIntAndCheck() 207 | if err != nil { 208 | return 209 | } 210 | } 211 | return 212 | } 213 | 214 | func ReadPMTDescriptor(lr *io.LimitedReader) (Desc []MpegTsDescriptor, err error) { 215 | var desc MpegTsDescriptor 216 | for lr.N > 0 { 217 | // tag (8) 218 | desc.Tag, err = util.ReadByteToUint8(lr) 219 | if err != nil { 220 | return 221 | } 222 | 223 | // length (8) 224 | desc.Length, err = util.ReadByteToUint8(lr) 225 | if err != nil { 226 | return 227 | } 228 | 229 | desc.Data = make([]byte, desc.Length) 230 | _, err = lr.Read(desc.Data) 231 | if err != nil { 232 | return 233 | } 234 | 235 | Desc = append(Desc, desc) 236 | } 237 | 238 | return 239 | } 240 | 241 | func WritePMTDescriptor(w io.Writer, descs []MpegTsDescriptor) (err error) { 242 | for _, desc := range descs { 243 | // tag(8) 244 | if err = util.WriteUint8ToByte(w, desc.Tag); err != nil { 245 | return 246 | } 247 | 248 | // length (8) 249 | if err = util.WriteUint8ToByte(w, uint8(len(desc.Data))); err != nil { 250 | return 251 | } 252 | 253 | // data 254 | if _, err = w.Write(desc.Data); err != nil { 255 | return 256 | } 257 | } 258 | 259 | return 260 | } 261 | 262 | func WritePMTBody(w io.Writer, pmt MpegTsPMT) (err error) { 263 | // reserved3(3) + pcrPID(13) 264 | if err = util.WriteUint16ToByte(w, pmt.PcrPID|7<<13, true); err != nil { 265 | return 266 | } 267 | 268 | // programInfoDescriptor 节目信息描述,字节数不能确定 269 | bw := &bytes.Buffer{} 270 | if err = WritePMTDescriptor(bw, pmt.ProgramInfoDescriptor); err != nil { 271 | return 272 | } 273 | 274 | pmt.ProgramInfoLength = uint16(bw.Len()) 275 | 276 | // reserved4(4) + programInfoLength(12) 277 | // programInfoLength(12) == 0x00(固定为0) + programInfoLength(10) 278 | if err = util.WriteUint16ToByte(w, pmt.ProgramInfoLength|0xf000, true); err != nil { 279 | return 280 | } 281 | 282 | // programInfoDescriptor 283 | if _, err = w.Write(bw.Bytes()); err != nil { 284 | return 285 | } 286 | 287 | // 循环读取所有的流的信息(音频或者视频) 288 | for _, esinfo := range pmt.Stream { 289 | // streamType(8) 290 | if err = util.WriteUint8ToByte(w, esinfo.StreamType); err != nil { 291 | return 292 | } 293 | 294 | // reserved5(3) + elementaryPID(13) 295 | if err = util.WriteUint16ToByte(w, esinfo.ElementaryPID|7<<13, true); err != nil { 296 | return 297 | } 298 | 299 | // descriptor ES流信息描述,字节数不能确定 300 | bw := &bytes.Buffer{} 301 | if err = WritePMTDescriptor(bw, esinfo.Descriptor); err != nil { 302 | return 303 | } 304 | 305 | esinfo.EsInfoLength = uint16(bw.Len()) 306 | 307 | // reserved6(4) + esInfoLength(12) 308 | // esInfoLength(12) == 0x00(固定为0) + esInfoLength(10) 309 | if err = util.WriteUint16ToByte(w, esinfo.EsInfoLength|0xf000, true); err != nil { 310 | return 311 | } 312 | 313 | // descriptor 314 | if _, err = w.Write(bw.Bytes()); err != nil { 315 | return 316 | } 317 | } 318 | 319 | return 320 | } 321 | 322 | func WritePMT(w io.Writer, pmt MpegTsPMT) (err error) { 323 | bw := &bytes.Buffer{} 324 | 325 | if err = WritePMTBody(bw, pmt); err != nil { 326 | return 327 | } 328 | 329 | if pmt.SectionLength == 0 { 330 | pmt.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes())) 331 | } 332 | 333 | psi := MpegTsPSI{} 334 | 335 | psi.Pmt = pmt 336 | 337 | if err = WritePSI(w, PSI_TYPE_PMT, psi, bw.Bytes()); err != nil { 338 | return 339 | } 340 | 341 | return 342 | } 343 | 344 | func WritePMTPacket(w io.Writer, tsHeader []byte, pmt MpegTsPMT) (err error) { 345 | if pmt.TableID != TABLE_TSPMS { 346 | err = errors.New("PMT table ID error") 347 | return 348 | } 349 | 350 | // 将所有要写的数据(PMT),全部放入到buffer中去. 351 | // buffer 里面已经写好了整个PMT表(PointerField+PSI+PMT+CRC) 352 | bw := &bytes.Buffer{} 353 | if err = WritePMT(bw, pmt); err != nil { 354 | return 355 | } 356 | 357 | // TODO:如果Pmt.Stream里面包含的信息很大,大于188? 358 | stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len()) 359 | 360 | var PMTPacket []byte 361 | PMTPacket = append(PMTPacket, tsHeader...) 362 | PMTPacket = append(PMTPacket, bw.Bytes()...) 363 | PMTPacket = append(PMTPacket, stuffingBytes...) 364 | 365 | fmt.Println("-------------------------") 366 | fmt.Println("Write PMT :", PMTPacket) 367 | fmt.Println("-------------------------") 368 | 369 | // 写PMT负载 370 | if _, err = w.Write(PMTPacket); err != nil { 371 | return 372 | } 373 | 374 | return 375 | } 376 | 377 | func WriteDefaultPMTPacket(w io.Writer) (err error) { 378 | _, err = w.Write(DefaultPMTPacket) 379 | if err != nil { 380 | return 381 | } 382 | 383 | return 384 | } 385 | -------------------------------------------------------------------------------- /codecs/mpegts/mpegts_psi.go: -------------------------------------------------------------------------------- 1 | package mpegts 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | 9 | "github.com/micro-community/stream/util" 10 | ) 11 | 12 | // 13 | // PSI 14 | // 15 | 16 | const ( 17 | PSI_TYPE_PAT = 1 18 | PSI_TYPE_PMT = 2 19 | PSI_TYPE_NIT = 3 20 | PSI_TYPE_CAT = 4 21 | PSI_TYPE_TST = 5 22 | PSI_TYPE_IPMP_CIT = 6 23 | ) 24 | 25 | type MpegTsPSI struct { 26 | // PAT 27 | // PMT 28 | // CAT 29 | // NIT 30 | Pat MpegTsPAT 31 | Pmt MpegTsPMT 32 | } 33 | 34 | // 当传输流包有效载荷包含 PSI 数据时,payload_unit_start_indicator 具有以下意义: 35 | // 若传输流包承载 PSI分段的首字节,则 payload_unit_start_indicator 值必为 1,指示此传输流包的有效载荷的首字节承载pointer_field. 36 | // 若传输流包不承载 PSI 分段的首字节,则 payload_unit_start_indicator 值必为 0,指示在此有效载荷中不存在 pointer_field 37 | // 只要是PSI就一定会有pointer_field 38 | func ReadPSI(r io.Reader, pt uint32) (lr *io.LimitedReader, psi MpegTsPSI, err error) { 39 | // pointer field(8) 40 | cr, ok := r.(*util.Crc32Reader) 41 | if ok { 42 | r = cr.R 43 | } 44 | pointer_field, err := util.ReadByteToUint8(r) 45 | if err != nil { 46 | return 47 | } 48 | 49 | if pointer_field != 0 { 50 | // 无论如何都应该确保能将pointer_field读取到,并且io.Reader指针向下移动 51 | // ioutil.Discard常用在,http中,如果Get请求,获取到了很大的Body,要丢弃Body,就用这个方法. 52 | // 因为http默认重链接的时候,必须等body读取完成. 53 | // 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据 54 | if _, err = io.CopyN(ioutil.Discard, r, int64(pointer_field)); err != nil { 55 | return 56 | } 57 | } 58 | if ok { 59 | r = cr 60 | } 61 | 62 | // table id(8) 63 | tableId, err := util.ReadByteToUint8(r) 64 | if err != nil { 65 | return 66 | } 67 | 68 | // sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) 69 | // sectionLength 前两个字节固定为00 70 | sectionSyntaxIndicatorAndSectionLength, err := util.ReadByteToUint16(r, true) 71 | if err != nil { 72 | return 73 | } 74 | 75 | // 指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC 76 | // 因此剩下最多只能在读sectionLength长度的字节 77 | lr = &io.LimitedReader{R: r, N: int64(sectionSyntaxIndicatorAndSectionLength & 0x3FF)} 78 | 79 | // PAT TransportStreamID(16) or PMT ProgramNumber(16) 80 | transportStreamIdOrProgramNumber, err := util.ReadByteToUint16(lr, true) 81 | if err != nil { 82 | return 83 | } 84 | 85 | // reserved2(2) + versionNumber(5) + currentNextIndicator(1) 86 | versionNumberAndCurrentNextIndicator, err := util.ReadByteToUint8(lr) 87 | if err != nil { 88 | return 89 | } 90 | 91 | // sectionNumber(8) 92 | sectionNumber, err := util.ReadByteToUint8(lr) 93 | if err != nil { 94 | return 95 | } 96 | 97 | // lastSectionNumber(8) 98 | lastSectionNumber, err := util.ReadByteToUint8(lr) 99 | if err != nil { 100 | return 101 | } 102 | 103 | // 因为lr.N是从sectionLength开始计算,所以要减去 pointer_field(8) + table id(8) + sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) 104 | lr.N -= 4 105 | 106 | switch pt { 107 | case PSI_TYPE_PAT: 108 | { 109 | if tableId != TABLE_PAS { 110 | err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId)) 111 | return 112 | } 113 | 114 | psi.Pat.TableID = tableId 115 | psi.Pat.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15) 116 | psi.Pat.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF 117 | psi.Pat.TransportStreamID = transportStreamIdOrProgramNumber 118 | psi.Pat.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e 119 | psi.Pat.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01 120 | psi.Pat.SectionNumber = sectionNumber 121 | psi.Pat.LastSectionNumber = lastSectionNumber 122 | } 123 | case PSI_TYPE_PMT: 124 | { 125 | if tableId != TABLE_TSPMS { 126 | err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId)) 127 | return 128 | } 129 | 130 | psi.Pmt.TableID = tableId 131 | psi.Pmt.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15) 132 | psi.Pmt.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF 133 | psi.Pmt.ProgramNumber = transportStreamIdOrProgramNumber 134 | psi.Pmt.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e 135 | psi.Pmt.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01 136 | psi.Pmt.SectionNumber = sectionNumber 137 | psi.Pmt.LastSectionNumber = lastSectionNumber 138 | } 139 | } 140 | 141 | return 142 | } 143 | 144 | func WritePSI(w io.Writer, pt uint32, psi MpegTsPSI, data []byte) (err error) { 145 | var tableId, versionNumberAndCurrentNextIndicator, sectionNumber, lastSectionNumber uint8 146 | var sectionSyntaxIndicatorAndSectionLength, transportStreamIdOrProgramNumber uint16 147 | 148 | switch pt { 149 | case PSI_TYPE_PAT: 150 | { 151 | if psi.Pat.TableID != TABLE_PAS { 152 | err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 0", tableId)) 153 | return 154 | } 155 | 156 | tableId = psi.Pat.TableID 157 | sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pat.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pat.SectionLength 158 | transportStreamIdOrProgramNumber = psi.Pat.TransportStreamID 159 | versionNumberAndCurrentNextIndicator = psi.Pat.VersionNumber<<1 | psi.Pat.CurrentNextIndicator 160 | sectionNumber = psi.Pat.SectionNumber 161 | lastSectionNumber = psi.Pat.LastSectionNumber 162 | } 163 | case PSI_TYPE_PMT: 164 | { 165 | if psi.Pmt.TableID != TABLE_TSPMS { 166 | err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 2", tableId)) 167 | return 168 | } 169 | 170 | tableId = psi.Pmt.TableID 171 | sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pmt.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pmt.SectionLength 172 | transportStreamIdOrProgramNumber = psi.Pmt.ProgramNumber 173 | versionNumberAndCurrentNextIndicator = psi.Pmt.VersionNumber<<1 | psi.Pmt.CurrentNextIndicator 174 | sectionNumber = psi.Pmt.SectionNumber 175 | lastSectionNumber = psi.Pmt.LastSectionNumber 176 | } 177 | } 178 | 179 | // pointer field(8) 180 | if err = util.WriteUint8ToByte(w, 0); err != nil { 181 | return 182 | } 183 | 184 | cw := &util.Crc32Writer{W: w, Crc32: 0xffffffff} 185 | 186 | // table id(8) 187 | if err = util.WriteUint8ToByte(cw, tableId); err != nil { 188 | return 189 | } 190 | 191 | // sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) 192 | // sectionLength 前两个字节固定为00 193 | // 1 0 11 sectionLength 194 | if err = util.WriteUint16ToByte(cw, sectionSyntaxIndicatorAndSectionLength, true); err != nil { 195 | return 196 | } 197 | 198 | // PAT TransportStreamID(16) or PMT ProgramNumber(16) 199 | if err = util.WriteUint16ToByte(cw, transportStreamIdOrProgramNumber, true); err != nil { 200 | return 201 | } 202 | 203 | // reserved2(2) + versionNumber(5) + currentNextIndicator(1) 204 | // 0x3 << 6 -> 1100 0000 205 | // 0x3 << 6 | 1 -> 1100 0001 206 | if err = util.WriteUint8ToByte(cw, versionNumberAndCurrentNextIndicator); err != nil { 207 | return 208 | } 209 | 210 | // sectionNumber(8) 211 | if err = util.WriteUint8ToByte(cw, sectionNumber); err != nil { 212 | return 213 | } 214 | 215 | // lastSectionNumber(8) 216 | if err = util.WriteUint8ToByte(cw, lastSectionNumber); err != nil { 217 | return 218 | } 219 | 220 | // data 221 | if _, err = cw.Write(data); err != nil { 222 | return 223 | } 224 | 225 | // crc32 226 | crc32 := util.BigLittleSwap(uint(cw.Crc32)) 227 | if err = util.WriteUint32ToByte(cw, uint32(crc32), true); err != nil { 228 | return 229 | } 230 | 231 | return 232 | } 233 | -------------------------------------------------------------------------------- /codecs/sps.go: -------------------------------------------------------------------------------- 1 | package codecs 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/micro-community/stream/util/bits" 7 | ) 8 | 9 | // SPSInfore 10 | type SPSInfo struct { 11 | ProfileIdc uint 12 | LevelIdc uint 13 | 14 | MbWidth uint 15 | MbHeight uint 16 | 17 | CropLeft uint 18 | CropRight uint 19 | CropTop uint 20 | CropBottom uint 21 | 22 | Width uint 23 | Height uint 24 | } 25 | 26 | func ParseSPS(data []byte) (self SPSInfo, err error) { 27 | r := &bits.GolombBitReader{R: bytes.NewReader(data)} 28 | 29 | if _, err = r.ReadBits(8); err != nil { 30 | return 31 | } 32 | 33 | if self.ProfileIdc, err = r.ReadBits(8); err != nil { 34 | return 35 | } 36 | 37 | // constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits 38 | if _, err = r.ReadBits(8); err != nil { 39 | return 40 | } 41 | 42 | // level_idc 43 | if self.LevelIdc, err = r.ReadBits(8); err != nil { 44 | return 45 | } 46 | 47 | // seq_parameter_set_id 48 | if _, err = r.ReadExponentialGolombCode(); err != nil { 49 | return 50 | } 51 | 52 | if self.ProfileIdc == 100 || self.ProfileIdc == 110 || 53 | self.ProfileIdc == 122 || self.ProfileIdc == 244 || 54 | self.ProfileIdc == 44 || self.ProfileIdc == 83 || 55 | self.ProfileIdc == 86 || self.ProfileIdc == 118 { 56 | 57 | var chroma_format_idc uint 58 | if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil { 59 | return 60 | } 61 | 62 | if chroma_format_idc == 3 { 63 | // residual_colour_transform_flag 64 | if _, err = r.ReadBit(); err != nil { 65 | return 66 | } 67 | } 68 | 69 | // bit_depth_luma_minus8 70 | if _, err = r.ReadExponentialGolombCode(); err != nil { 71 | return 72 | } 73 | // bit_depth_chroma_minus8 74 | if _, err = r.ReadExponentialGolombCode(); err != nil { 75 | return 76 | } 77 | // qpprime_y_zero_transform_bypass_flag 78 | if _, err = r.ReadBit(); err != nil { 79 | return 80 | } 81 | 82 | var seq_scaling_matrix_present_flag uint 83 | if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil { 84 | return 85 | } 86 | 87 | if seq_scaling_matrix_present_flag != 0 { 88 | for i := 0; i < 8; i++ { 89 | var seq_scaling_list_present_flag uint 90 | if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil { 91 | return 92 | } 93 | if seq_scaling_list_present_flag != 0 { 94 | var sizeOfScalingList uint 95 | if i < 6 { 96 | sizeOfScalingList = 16 97 | } else { 98 | sizeOfScalingList = 64 99 | } 100 | lastScale := uint(8) 101 | nextScale := uint(8) 102 | for j := uint(0); j < sizeOfScalingList; j++ { 103 | if nextScale != 0 { 104 | var delta_scale uint 105 | if delta_scale, err = r.ReadSE(); err != nil { 106 | return 107 | } 108 | nextScale = (lastScale + delta_scale + 256) % 256 109 | } 110 | if nextScale != 0 { 111 | lastScale = nextScale 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | // log2_max_frame_num_minus4 120 | if _, err = r.ReadExponentialGolombCode(); err != nil { 121 | return 122 | } 123 | 124 | var pic_order_cnt_type uint 125 | if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil { 126 | return 127 | } 128 | if pic_order_cnt_type == 0 { 129 | // log2_max_pic_order_cnt_lsb_minus4 130 | if _, err = r.ReadExponentialGolombCode(); err != nil { 131 | return 132 | } 133 | } else if pic_order_cnt_type == 1 { 134 | // delta_pic_order_always_zero_flag 135 | if _, err = r.ReadBit(); err != nil { 136 | return 137 | } 138 | // offset_for_non_ref_pic 139 | if _, err = r.ReadSE(); err != nil { 140 | return 141 | } 142 | // offset_for_top_to_bottom_field 143 | if _, err = r.ReadSE(); err != nil { 144 | return 145 | } 146 | var num_ref_frames_in_pic_order_cnt_cycle uint 147 | if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil { 148 | return 149 | } 150 | for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ { 151 | if _, err = r.ReadSE(); err != nil { 152 | return 153 | } 154 | } 155 | } 156 | 157 | // max_num_ref_frames 158 | if _, err = r.ReadExponentialGolombCode(); err != nil { 159 | return 160 | } 161 | 162 | // gaps_in_frame_num_value_allowed_flag 163 | if _, err = r.ReadBit(); err != nil { 164 | return 165 | } 166 | 167 | if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil { 168 | return 169 | } 170 | self.MbWidth++ 171 | 172 | if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil { 173 | return 174 | } 175 | self.MbHeight++ 176 | 177 | var frame_mbs_only_flag uint 178 | if frame_mbs_only_flag, err = r.ReadBit(); err != nil { 179 | return 180 | } 181 | if frame_mbs_only_flag == 0 { 182 | // mb_adaptive_frame_field_flag 183 | if _, err = r.ReadBit(); err != nil { 184 | return 185 | } 186 | } 187 | 188 | // direct_8x8_inference_flag 189 | if _, err = r.ReadBit(); err != nil { 190 | return 191 | } 192 | 193 | var frame_cropping_flag uint 194 | if frame_cropping_flag, err = r.ReadBit(); err != nil { 195 | return 196 | } 197 | if frame_cropping_flag != 0 { 198 | if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil { 199 | return 200 | } 201 | if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil { 202 | return 203 | } 204 | if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil { 205 | return 206 | } 207 | if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil { 208 | return 209 | } 210 | } 211 | 212 | self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2 213 | self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2 214 | 215 | return 216 | } 217 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # 支持零配置启动,即无需填写配置默认启用所有的插件。 2 | 3 | global: 4 | console: 5 | secret: "ab0f6913670062af4d2f15c62120517c" 6 | # http: 7 | # listenaddrtls: :8081 8 | # certfile: somesite.pem 9 | # keyfile: somesite.key 10 | webtransport: 11 | enable: false 12 | rtsp: 13 | enable: false 14 | pull: 15 | pullonstart: true 16 | pulllist: 17 | live/rtsp: rtsp://admin:MOMA2021@47.94.226.17/Streaming/Channels/201?transportmode=unicast 18 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "enabledLanguageIds": [ 4 | "go", 5 | "javascript", 6 | "markdown", 7 | "plaintext", 8 | "yml", 9 | "toml" 10 | ], 11 | "enabled": true, 12 | "words": [ 13 | "aacsent", 14 | "aapi", 15 | "ACMECA", 16 | "ahandler", 17 | "ahttp", 18 | "arpc", 19 | "AVCC", 20 | "avcsent", 21 | "avformat", 22 | "AVRing", 23 | "bbolt", 24 | "bname", 25 | "boltdb", 26 | "botc", 27 | "bpool", 28 | "btree", 29 | "cbytes", 30 | "ccli", 31 | "cfstore", 32 | "chzyer", 33 | "clic", 34 | "cmds", 35 | "cmucp", 36 | "cors", 37 | "cseq", 38 | "Debugf", 39 | "etcd", 40 | "evar", 41 | "favicon", 42 | "Fmtp", 43 | "fname", 44 | "fopts", 45 | "gerr", 46 | "githubusercontent", 47 | "Golomb", 48 | "gopsutil", 49 | "gostore", 50 | "grpc", 51 | "gzipped", 52 | "Hdlr", 53 | "Hubot", 54 | "indexs", 55 | "Infof", 56 | "JWTs", 57 | "kubernetes", 58 | "logrusorgru", 59 | "maddr", 60 | "mattn", 61 | "mdns", 62 | "mhttp", 63 | "mkdirp", 64 | "mname", 65 | "mnet", 66 | "Monibuca", 67 | "mpegts", 68 | "mucp", 69 | "muxer", 70 | "NALU", 71 | "nedge", 72 | "nserver", 73 | "packetization", 74 | "PCMA", 75 | "PCMU", 76 | "pframe", 77 | "pinfo", 78 | "proto", 79 | "protobuf", 80 | "protoc", 81 | "psock", 82 | "rbuf", 83 | "rcodec", 84 | "rdata", 85 | "remb", 86 | "rerr", 87 | "ropts", 88 | "rrmicro", 89 | "rtcp", 90 | "RTMP", 91 | "rtphdr", 92 | "RTSP", 93 | "runc", 94 | "serenize", 95 | "sess", 96 | "shirou", 97 | "smucp", 98 | "snaker", 99 | "sprop", 100 | "SSRC", 101 | "STAPA", 102 | "storecli", 103 | "streamserver", 104 | "streamuuid", 105 | "streetsidesoftware", 106 | "tmpl", 107 | "Tracef", 108 | "tsmerge", 109 | "upgrader", 110 | "urfave", 111 | "uuid", 112 | "vals", 113 | "visualstudio", 114 | "vsmarketplacebadge", 115 | "webrtc", 116 | "webrtcv" 117 | ], 118 | "flagWords": [ 119 | "hte" 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/micro-community/stream 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 7 | github.com/gorilla/websocket v1.5.0 8 | github.com/logrusorgru/aurora v2.0.3+incompatible 9 | github.com/mattn/go-colorable v0.1.13 10 | github.com/pion/interceptor v0.1.12 11 | github.com/pion/rtp v1.7.13 12 | github.com/pkg/errors v0.9.1 13 | github.com/shirou/gopsutil v2.21.11+incompatible 14 | github.com/urfave/cli/v2 v2.11.2 15 | go-micro.dev/v4 v4.8.1 16 | gopkg.in/yaml.v3 v3.0.1 17 | ) 18 | 19 | require ( 20 | github.com/cloudflare/circl v1.2.0 // indirect 21 | github.com/pion/ice/v2 v2.2.7 // indirect 22 | github.com/pion/rtcp v1.2.10 // indirect 23 | github.com/pion/sdp/v3 v3.0.6 // indirect 24 | github.com/pion/srtp/v2 v2.0.10 // indirect 25 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 26 | golang.org/x/tools v0.1.12 // indirect 27 | golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect 28 | ) 29 | 30 | require ( 31 | github.com/Microsoft/go-winio v0.5.2 // indirect 32 | github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect 33 | github.com/acomagu/bufpipe v1.0.3 // indirect 34 | github.com/bitly/go-simplejson v0.5.0 // indirect 35 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 36 | github.com/emirpasic/gods v1.18.1 // indirect 37 | github.com/fsnotify/fsnotify v1.5.4 // indirect 38 | github.com/funny/utest v0.0.0-20161029064919-43870a374500 // indirect 39 | github.com/go-git/gcfg v1.5.0 // indirect 40 | github.com/go-git/go-billy/v5 v5.3.1 // indirect 41 | github.com/go-git/go-git/v5 v5.4.2 // indirect 42 | github.com/go-ole/go-ole v1.2.6 // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/google/uuid v1.3.0 // indirect 45 | github.com/imdario/mergo v0.3.13 // indirect 46 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 47 | github.com/kevinburke/ssh_config v1.2.0 // indirect 48 | github.com/mattn/go-isatty v0.0.16 // indirect 49 | github.com/miekg/dns v1.1.50 // indirect 50 | github.com/mitchellh/go-homedir v1.1.0 // indirect 51 | github.com/nxadm/tail v1.4.8 // indirect 52 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 53 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 54 | github.com/pion/datachannel v1.5.2 // indirect 55 | github.com/pion/dtls/v2 v2.1.5 // indirect 56 | github.com/pion/logging v0.2.2 // indirect 57 | github.com/pion/mdns v0.0.5 // indirect 58 | github.com/pion/randutil v0.1.0 // indirect 59 | github.com/pion/sctp v1.8.2 // indirect 60 | github.com/pion/stun v0.3.5 // indirect 61 | github.com/pion/transport v0.13.1 // indirect 62 | github.com/pion/turn/v2 v2.0.8 // indirect 63 | github.com/pion/udp v0.1.1 // indirect 64 | github.com/pion/webrtc/v3 v3.1.43 65 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 66 | github.com/sergi/go-diff v1.2.0 // indirect 67 | github.com/tklauser/go-sysconf v0.3.10 // indirect 68 | github.com/tklauser/numcpus v0.5.0 // indirect 69 | github.com/xanzy/ssh-agent v0.3.2 // indirect 70 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 71 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 72 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect 73 | golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect 74 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect 75 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 76 | golang.org/x/text v0.3.7 // indirect 77 | google.golang.org/protobuf v1.28.1 // indirect 78 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 79 | gopkg.in/warnings.v0 v0.1.2 // indirect 80 | ) 81 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/micro-community/stream/app" 8 | "github.com/micro-community/stream/util" 9 | "github.com/micro-community/stream/ws" 10 | "github.com/urfave/cli/v2" 11 | "go-micro.dev/v4" 12 | "go-micro.dev/v4/util/log" 13 | "go-micro.dev/v4/web" 14 | ) 15 | 16 | // Meta Data 17 | var ( 18 | Name = "x-streaming" 19 | Address = ":8080" 20 | ) 21 | 22 | // this is still a toy code block 23 | func main() { 24 | 25 | //replace inner service 26 | s := micro.NewService( 27 | micro.Name("stream"), 28 | micro.HandleSignal(false), 29 | micro.Flags( 30 | &cli.StringFlag{ 31 | Name: "version", 32 | }, 33 | &cli.BoolFlag{ 34 | Name: "timeout", 35 | }, 36 | ), 37 | ) 38 | 39 | s.Init(micro.Action(func(c *cli.Context) error { 40 | app.Version = c.String("version") 41 | return nil 42 | })) 43 | 44 | //support websocket directly,by go-micro 45 | srv := web.NewService(web.MicroService(s)) 46 | // static files 47 | srv.Handle("/", http.FileServer(http.Dir("html"))) 48 | // Handle websocket connection 49 | srv.HandleFunc("/ws", ws.HandleConn) 50 | 51 | srv.Init() 52 | 53 | ctx, cancel := context.WithCancel(context.WithValue(context.Background(), "version", app.Version)) 54 | go util.WaitTerm(cancel) 55 | //toy code ,will be changed. 56 | go Run(ctx, "config.yaml") 57 | 58 | // Run service 59 | if err := srv.Run(); err != nil { 60 | log.Fatal(err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /media/dtse.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-05 17:23:16 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:23:48 6 | * @FilePath: \stream\media\dtse.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package media 11 | 12 | // DTSEstimator is a DTS estimator. 13 | type DTSEstimator struct { 14 | prevDTS uint32 15 | prevPTS uint32 16 | prevPrevPTS uint32 17 | dts func(uint32) uint32 18 | delta uint32 19 | } 20 | -------------------------------------------------------------------------------- /media/frame.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 11:35:19 6 | * @FilePath: \stream\media\frame.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package media 11 | 12 | import ( 13 | "net" 14 | "time" 15 | 16 | "github.com/pion/rtp" 17 | ) 18 | 19 | const ( 20 | SUBTYPE_RAW = iota 21 | SUBTYPE_AVCC 22 | SUBTYPE_RTP 23 | SUBTYPE_FLV 24 | ) 25 | 26 | type NALUSlice net.Buffers 27 | type AudioSlice []byte 28 | 29 | type AVCCFrame []byte // 一帧AVCC格式的数据 30 | type AnnexBFrame []byte // 一帧AnnexB格式数据 31 | // RawSlice 原始切片数据 32 | type RawSlice interface { 33 | ~[][]byte | ~[]byte 34 | } 35 | 36 | type RTPFrame struct { 37 | rtp.Packet 38 | } 39 | 40 | // DecoderConfiguration for decode AV Data 41 | type DecoderConfiguration[T RawSlice] struct { 42 | PayloadType byte 43 | AVCC net.Buffers 44 | Raw T 45 | FLV net.Buffers 46 | Seq int //收到第几个序列帧,用于变码率时让订阅者发送序列帧 47 | } 48 | 49 | // FrameBase for Media Data 50 | type FrameBase struct { 51 | DeltaTime uint32 // 相对上一帧时间戳,毫秒 52 | AbsTime uint32 // 绝对时间戳,毫秒 53 | Timestamp time.Time // 写入时间,可用于比较两个帧的先后 54 | Sequence uint32 // 在一个Track中的序号 55 | BytesIn int // 输入字节数用于计算BPS 56 | } 57 | type AVFrame[T RawSlice] struct { 58 | FrameBase 59 | IFrame bool 60 | PTS uint32 61 | DTS uint32 62 | AVCC net.Buffers `json:"-"` // 打包好的AVCC格式 63 | RTP []*RTPFrame `json:"-"` 64 | Raw []T `json:"-"` // 裸数据 65 | canRead bool 66 | } 67 | 68 | // Define some regular type for frame 69 | type AudioFrame AVFrame[AudioSlice] 70 | type VideoFrame AVFrame[NALUSlice] 71 | type AudioDeConf DecoderConfiguration[AudioSlice] 72 | type VideoDeConf DecoderConfiguration[NALUSlice] 73 | type FLVFrame net.Buffers 74 | type AudioRTP RTPFrame 75 | type VideoRTP RTPFrame 76 | -------------------------------------------------------------------------------- /media/ring.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/micro-community/stream/algo" 7 | ) 8 | 9 | type RingBuffer[T any] struct { 10 | *algo.Ring[T] `json:"-"` 11 | Size int 12 | MoveCount uint32 13 | LastValue *T 14 | } 15 | 16 | type AVRing[T RawSlice] struct { 17 | RingBuffer[AVFrame[T]] 18 | Poll time.Duration 19 | } 20 | -------------------------------------------------------------------------------- /media/stream.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 11:13:58 6 | * @FilePath: \stream\media\stream.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package media 11 | 12 | import "time" 13 | 14 | type StreamState byte 15 | type StreamAction byte 16 | 17 | // 四状态机 18 | const ( 19 | STATE_WAITPUBLISH StreamState = iota // 等待发布者状态 20 | STATE_PUBLISHING // 正在发布流状态 21 | STATE_WAITCLOSE // 等待关闭状态(自动关闭延时开启) 22 | STATE_CLOSED // 流已关闭,不可使用 23 | ) 24 | 25 | const ( 26 | ACTION_PUBLISH StreamAction = iota 27 | ACTION_TIMEOUT // 发布流长时间没有数据/长时间没有发布者发布流/等待关闭时间到 28 | ACTION_PUBLISHLOST // 发布者意外断开 29 | ACTION_CLOSE // 主动关闭流 30 | ACTION_LASTLEAVE // 最后一个订阅者离开 31 | ACTION_FIRSTENTER // 第一个订阅者进入 32 | ) 33 | 34 | type StreamSummary struct { 35 | Path string 36 | State StreamState 37 | Subscribers int 38 | Tracks []string 39 | StartTime time.Time 40 | Type string 41 | BPS int 42 | } 43 | 44 | type IStream interface { 45 | AddTrack(Track) 46 | RemoveTrack(Track) 47 | IsClosed() bool 48 | SSRC() uint32 49 | Receive(any) bool 50 | } 51 | -------------------------------------------------------------------------------- /media/track.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 11:02:49 6 | * @FilePath: \stream\media\track.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package media 11 | 12 | import ( 13 | "time" 14 | 15 | "github.com/micro-community/stream/algo" 16 | "github.com/pion/rtp" 17 | ) 18 | 19 | type TimelineData[T any] struct { 20 | Timestamp time.Time 21 | Value T 22 | } 23 | 24 | // TrackBase 基础Track类 25 | type TrackBase struct { 26 | Name string 27 | Stream IStream `json:"-"` //所属Stream 28 | ts time.Time 29 | bytes int 30 | frames int 31 | BPS int 32 | FPS int 33 | RawPart []int // 裸数据片段用于UI上显示 34 | RawSize int // 裸数据长度 35 | BPSs []TimelineData[int] // 10s码率统计 36 | FPSs []TimelineData[int] // 10s帧率统计 37 | } 38 | 39 | type Track interface { 40 | GetBase() *TrackBase 41 | LastWriteTime() time.Time 42 | SnapForJson() 43 | } 44 | 45 | // AVTrack for audio and video 46 | type AVTrack interface { 47 | Track 48 | Attach() 49 | Detach() 50 | WriteAVCC(ts uint32, frame AVCCFrame) //写入AVCC格式的数据 51 | WriteRTP([]byte) 52 | WriteRTPPack(*rtp.Packet) 53 | Flush() 54 | } 55 | 56 | // VideoTrack for Video 57 | type VideoTrack interface { 58 | AVTrack 59 | GetDecoderConfiguration() DecoderConfiguration[NALUSlice] 60 | CurrentFrame() *AVFrame[NALUSlice] 61 | PreFrame() *AVFrame[NALUSlice] 62 | WriteSlice(NALUSlice) 63 | WriteAnnexB(uint32, uint32, AnnexBFrame) 64 | } 65 | 66 | type AudioTrack interface { 67 | AVTrack 68 | GetDecoderConfiguration() DecoderConfiguration[AudioSlice] 69 | CurrentFrame() *AVFrame[AudioSlice] 70 | PreFrame() *AVFrame[AudioSlice] 71 | WriteSlice(AudioSlice) 72 | WriteADTS([]byte) 73 | } 74 | 75 | // Tracks for streams 76 | type Tracks struct { 77 | algo.Map[string, Track] 78 | } 79 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io/ioutil" 7 | "path/filepath" 8 | goRuntime "runtime" 9 | "strings" 10 | 11 | "github.com/logrusorgru/aurora" // colorable 12 | "go-micro.dev/v4/util/log" 13 | "gopkg.in/yaml.v3" 14 | 15 | "github.com/micro-community/stream/app" 16 | "github.com/micro-community/stream/runtime" 17 | ) 18 | 19 | // Run process 20 | func Run(ctx context.Context, configFile string) (err error) { 21 | _, enginePath, _, _ := goRuntime.Caller(0) 22 | if parts := strings.Split(filepath.Dir(enginePath), "@"); len(parts) > 1 { 23 | app.Version = parts[len(parts)-1] 24 | } 25 | rawConfigData, err := ioutil.ReadFile(configFile) 26 | if err != nil { 27 | log.Info(aurora.Red("read config file error:"), err) 28 | return errors.New("read config file error") 29 | } 30 | 31 | //get config 32 | var conf app.Configuration 33 | if err := yaml.Unmarshal(rawConfigData, &conf); err != nil { 34 | log.Error("parsing yml files error:,system will use default", err) 35 | } 36 | 37 | log.Info(aurora.Green("start stream server"), aurora.BrightBlue(app.Version)) 38 | 39 | go runtime.Summary.StartSummary() 40 | 41 | for { 42 | select { 43 | case <-ctx.Done(): 44 | return 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /proto/README.md: -------------------------------------------------------------------------------- 1 | #README.md 2 | -------------------------------------------------------------------------------- /proto/session/session.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package monibuca; 4 | 5 | service monibuca { 6 | rpc Play(PlayRequest) returns (PlayResponse) {} 7 | rpc Stop(StopRequest) returns (StopResponse) {} 8 | rpc Pull(PullRequest) returns (PullResponse) {} 9 | } 10 | 11 | message PlayRequest { 12 | string id = 1; 13 | string username = 2; 14 | string password = 3; 15 | int32 streamid = 4; 16 | } 17 | 18 | message PlayResponse {} 19 | 20 | message StopRequest { 21 | string id = 1; 22 | int32 streamid = 2; 23 | } 24 | 25 | message StopResponse { 26 | string id = 1; 27 | } 28 | 29 | message PullRequest { 30 | int32 streamid = 1; 31 | } 32 | 33 | message PullResponse {} -------------------------------------------------------------------------------- /pubsub/channel.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 12:22:34 6 | * @FilePath: \stream\pubsub\channel.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package pubsub 11 | 12 | import ( 13 | "context" 14 | "io" 15 | "net/url" 16 | "time" 17 | ) 18 | 19 | // Client for Pubsub Options 20 | type Client[ClientOpt ClientOption] struct { 21 | Option *ClientOpt //客户端连接选项 22 | StreamPath string // 本地流标识 23 | RemoteURL string // 远程服务器地址(用于推拉) 24 | ReConnectCount int //重连次数 25 | } 26 | 27 | // IChannel for IIO to pub/sub stream 28 | type IChannel[ChannelOpt ChannelOption] interface { 29 | IsClosed() bool 30 | OnEvent(any) 31 | Stop() 32 | Set(any) 33 | SetParentCtx(context.Context) 34 | //receive 35 | receive(streamPathUrl string, channel IChannel[ChannelOpt], opt *ChannelOpt) error 36 | } 37 | 38 | // Channel with Option Handled 39 | type Channel[ChannelOpt ChannelOption] struct { 40 | ID string 41 | Type string 42 | Args url.Values 43 | StartTime time.Time //创建时间 44 | Stream *Stream `json:"-"` 45 | ChannelOption *ChannelOpt `json:"-"` 46 | Channel IChannel[ChannelOpt] `json:"-"` 47 | context.Context `json:"-"` //不要直接设置,应当通过OnEvent传入父级Context 48 | context.CancelFunc `json:"-"` //流关闭是关闭发布者或者订阅者 49 | io.Reader `json:"-"` 50 | io.Writer `json:"-"` 51 | io.Closer `json:"-"` 52 | } 53 | 54 | func (ch *Channel[ChannelOpt]) IsClosed() bool { 55 | return ch.Err() != nil 56 | } 57 | 58 | // handle Event from Stream 59 | func (ch *Channel[ChannelOpt]) OnEvent(any) { 60 | 61 | } 62 | 63 | func (ch *Channel[ChannelOpt]) Stop() { 64 | 65 | } 66 | 67 | // Set Writer、Reader、Closer 68 | func (ch *Channel[ChannelOpt]) Set(operator any) { 69 | if v, ok := operator.(io.Closer); ok { 70 | ch.Closer = v 71 | return 72 | } 73 | if v, ok := operator.(io.Reader); ok { 74 | ch.Reader = v 75 | return 76 | } 77 | if v, ok := operator.(io.Writer); ok { 78 | ch.Writer = v 79 | return 80 | } 81 | } 82 | 83 | func (ch *Channel[ChannelOpt]) SetParentCtx(context.Context) { 84 | 85 | } 86 | 87 | func (ch *Channel[ChannelOpt]) GetChannel() *Channel[ChannelOpt] { 88 | return ch 89 | } 90 | 91 | // receive from channel 92 | func (ch *Channel[ChannelOpt]) receive(streamPathUrl string, channel IChannel[ChannelOpt], opt *ChannelOpt) error { 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /pubsub/define.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/micro-community/stream/media" 7 | "github.com/micro-community/stream/track" 8 | ) 9 | 10 | type ReadType[T media.RawSlice] interface { 11 | GetDecConfSeq() int 12 | ReadRing() *media.AVRing[T] 13 | } 14 | 15 | type PlayContext[T ReadType[R], R media.RawSlice] struct { 16 | Track T 17 | ring *media.AVRing[R] 18 | confSeq int 19 | Frame **media.AVFrame[R] 20 | } 21 | type TrackPlayer struct { 22 | context.Context `json:"-"` 23 | context.CancelFunc `json:"-"` 24 | Audio PlayContext[*track.Audio, media.AudioSlice] 25 | Video PlayContext[*track.Video, media.NALUSlice] 26 | SkipTS uint32 //跳过的时间戳 27 | FirstAbsTS uint32 //订阅起始时间戳 28 | } 29 | -------------------------------------------------------------------------------- /pubsub/options.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | type PublishOption struct { 4 | PubAudio bool 5 | PubVideo bool 6 | KickExist bool // 是否踢掉已经存在的发布者 7 | PublishTimeout int // 发布无数据超时 8 | WaitCloseTimeout int // 延迟自动关闭(等待重连) 9 | DelayCloseTimeout int // 延迟自动关闭(无订阅时) 10 | } 11 | 12 | type SubscribeOption struct { 13 | SubAudio bool 14 | SubVideo bool 15 | LiveMode bool // 实时模式:追赶发布者进度,在播放首屏后等待发布者的下一个关键帧,然后调到该帧。 16 | IFrameOnly bool // 只要关键帧 17 | WaitTimeout int // 等待流超时 18 | } 19 | 20 | // PushOption for push stream 21 | type PushOption struct { 22 | RePush int // 断开后自动重推,0 表示不自动重推,-1 表示无限重推,高于0 的数代表最大重推次数 23 | PushList map[string]string // 自动推流列表 24 | } 25 | 26 | // PullOption for pull stream 27 | type PullOption struct { 28 | RePull int // 断开后自动重拉,0 表示不自动重拉,-1 表示无限重拉,高于0 的数代表最大重拉次数 29 | PullOnStart bool // 启动时拉流 30 | PullOnSubscribe bool // 订阅时自动拉流 31 | PullList map[string]string // 自动拉流列表,以streamPath为key,url为value 32 | } 33 | 34 | // ChannelOption 35 | type ChannelOption interface { 36 | PublishOption | SubscribeOption 37 | } 38 | 39 | // ClientOption 40 | type ClientOption interface { 41 | PullOption | PushOption 42 | } 43 | -------------------------------------------------------------------------------- /pubsub/publisher.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 12:20:50 6 | * @FilePath: \stream\pubsub\publisher.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package pubsub 11 | 12 | import "github.com/micro-community/stream/media" 13 | 14 | // Publish of Publisher 15 | type IPublish interface { 16 | IChannel[PublishOption] 17 | //get option 18 | // implemented in IChannel 19 | //GetOption() *PublishOption 20 | //receive stream from channe 21 | //Get the channel instance for publishing 22 | // implemented in IChannel 23 | //GetChannel() *Channel[PublishOption] 24 | 25 | //get track 26 | getAudioTrack() media.AudioTrack 27 | getVideoTrack() media.VideoTrack 28 | 29 | WriteAVCCVideo(ts uint32, frame media.AVCCFrame) 30 | WriteAVCCAudio(ts uint32, frame media.AVCCFrame) 31 | } 32 | 33 | // Publisher 发布者定义 34 | type Publisher struct { 35 | Channel[PublishOption] 36 | media.AudioTrack `json:"-"` 37 | media.VideoTrack `json:"-"` 38 | } 39 | 40 | func (p *Publisher) GetOption() *PublishOption { 41 | 42 | return nil 43 | } 44 | 45 | func (p *Publisher) Stop() { 46 | p.Channel.Stop() 47 | p.Stream.Receive(media.ACTION_PUBLISHLOST) 48 | } 49 | 50 | func (p *Publisher) getAudioTrack() media.AudioTrack { 51 | return p.AudioTrack 52 | } 53 | func (p *Publisher) getVideoTrack() media.VideoTrack { 54 | return p.VideoTrack 55 | } 56 | 57 | //WriteAVCCVideo Data Frame 58 | func (p *Publisher) WriteAVCCVideo(ts uint32, frame media.AVCCFrame) { 59 | 60 | } 61 | 62 | func (p *Publisher) WriteAVCCAudio(ts uint32, frame media.AVCCFrame) { 63 | 64 | } 65 | -------------------------------------------------------------------------------- /pubsub/puller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-06 10:14:25 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 10:38:46 6 | * @FilePath: \stream\pubsub\puller.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package pubsub 11 | 12 | type IPull interface { 13 | IPublish 14 | Connect() error 15 | Pull() 16 | Reconnect() bool 17 | init(streamPathUrl string, url string, option *PullOption) 18 | } 19 | 20 | //Puller for remote pull stream 21 | type Puller struct { 22 | Client[PullOption] 23 | } 24 | -------------------------------------------------------------------------------- /pubsub/pusher.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | type IPush interface { 4 | ISubscribe 5 | Push() error 6 | Connect() error 7 | init(string, string) 8 | Reconnect() bool 9 | } 10 | -------------------------------------------------------------------------------- /pubsub/stream.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-06 10:55:13 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 11:16:31 6 | * @FilePath: \stream\pubsub\stream.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package pubsub 11 | 12 | import ( 13 | "time" 14 | 15 | "github.com/micro-community/stream/algo" 16 | "github.com/micro-community/stream/media" 17 | ) 18 | 19 | // Stream 流定义 20 | type Stream struct { 21 | timeout *time.Timer //当前状态的超时定时器 22 | actionChan algo.SafeChan[any] 23 | StartTime time.Time //创建时间 24 | PublishTimeout time.Duration //发布者无数据后超时 25 | DelayCloseTimeout time.Duration //发布者丢失后等待 26 | Path string 27 | Publisher IPublish 28 | State media.StreamState 29 | Subscribers []ISubscribe // 订阅者 30 | Tracks media.Tracks 31 | AppName string 32 | StreamName string 33 | } 34 | 35 | func (s *Stream) Receive(event any) bool { 36 | //return s.actionChan.Send(event) 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /pubsub/subscriber.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 12:21:31 6 | * @FilePath: \stream\pubsub\subscriber.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package pubsub 11 | 12 | import "github.com/micro-community/stream/media" 13 | 14 | type ISubscribe interface { 15 | IChannel[SubscribeOption] 16 | IsPlaying() bool 17 | PlayRaw() 18 | PlayBlock(byte) 19 | PlayFLV() 20 | Stop() 21 | } 22 | 23 | // Subscriber 订阅者实体定义 24 | type Subscriber struct { 25 | Channel[SubscribeOption] 26 | TrackPlayer `json:"-"` 27 | } 28 | 29 | func (s *Subscriber) OnEvent(event any) { 30 | 31 | } 32 | 33 | func (s *Subscriber) AddTrack(t media.Track) bool { 34 | 35 | return false 36 | } 37 | 38 | func (s *Subscriber) IsPlaying() bool { 39 | return s.TrackPlayer.Context != nil && s.TrackPlayer.Err() == nil 40 | } 41 | 42 | func (s *Subscriber) PlayRaw() { 43 | 44 | } 45 | 46 | func (s *Subscriber) PlayFLV() { 47 | 48 | } 49 | 50 | func (s *Subscriber) PlayRTP() { 51 | 52 | } 53 | 54 | //PlayBlock 阻塞式读取数据 55 | func (s *Subscriber) PlayBlock(subType byte) { 56 | 57 | } 58 | -------------------------------------------------------------------------------- /runtime/arch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micro-community/stream/70e052996304df43a714413e6c2ca587eba28a86/runtime/arch.gif -------------------------------------------------------------------------------- /runtime/hook.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | var AuthHooks = make(AuthHook, 0) 4 | 5 | type AuthHook []func(string) error 6 | 7 | func (h AuthHook) AddHook(hook func(string) error) { 8 | AuthHooks = append(h, hook) 9 | } 10 | func (h AuthHook) Trigger(sign string) error { 11 | for _, f := range h { 12 | if err := f(sign); err != nil { 13 | return err 14 | } 15 | } 16 | return nil 17 | } 18 | 19 | var OnPublishHooks = make(OnPublishHook, 0) 20 | 21 | type OnPublishHook []func(r *Stream) 22 | 23 | func (h OnPublishHook) AddHook(hook func(r *Stream)) { 24 | OnPublishHooks = append(h, hook) 25 | } 26 | func (h OnPublishHook) Trigger(r *Stream) { 27 | for _, f := range h { 28 | f(r) 29 | } 30 | } 31 | 32 | var OnSubscribeHooks = make(OnSubscribeHook, 0) 33 | 34 | type OnSubscribeHook []func(s *Subscriber) 35 | 36 | func (h OnSubscribeHook) AddHook(hook func(s *Subscriber)) { 37 | OnSubscribeHooks = append(h, hook) 38 | } 39 | func (h OnSubscribeHook) Trigger(s *Subscriber) { 40 | for _, f := range h { 41 | f(s) 42 | } 43 | } 44 | 45 | var OnUnSubscribeHooks = make(OnUnSubscribeHook, 0) 46 | 47 | type OnUnSubscribeHook []func(s *Subscriber) 48 | 49 | func (h OnUnSubscribeHook) AddHook(hook func(s *Subscriber)) { 50 | OnUnSubscribeHooks = append(h, hook) 51 | } 52 | func (h OnUnSubscribeHook) Trigger(s *Subscriber) { 53 | for _, f := range h { 54 | f(s) 55 | } 56 | } 57 | 58 | var OnDropHooks = make(OnDropHook, 0) 59 | 60 | type OnDropHook []func(s *Subscriber) 61 | 62 | func (h OnDropHook) AddHook(hook func(s *Subscriber)) { 63 | OnDropHooks = append(h, hook) 64 | } 65 | func (h OnDropHook) Trigger(s *Subscriber) { 66 | for _, f := range h { 67 | f(s) 68 | } 69 | } 70 | 71 | var OnSummaryHooks = make(OnSummaryHook, 0) 72 | 73 | type OnSummaryHook []func(bool) 74 | 75 | func (h OnSummaryHook) AddHook(hook func(bool)) { 76 | OnSummaryHooks = append(h, hook) 77 | } 78 | func (h OnSummaryHook) Trigger(v bool) { 79 | for _, f := range h { 80 | f(v) 81 | } 82 | } 83 | 84 | var OnStreamClosedHooks = make(OnStreamClosedHook, 0) 85 | 86 | type OnStreamClosedHook []func(*Stream) 87 | 88 | func (h OnStreamClosedHook) AddHook(hook func(*Stream)) { 89 | OnStreamClosedHooks = append(h, hook) 90 | } 91 | func (h OnStreamClosedHook) Trigger(v *Stream) { 92 | for _, f := range h { 93 | f(v) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /runtime/logger.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "sync" 9 | 10 | "github.com/logrusorgru/aurora" 11 | "github.com/mattn/go-colorable" 12 | ) 13 | 14 | // LogWriter 多端写日志类 15 | type LogWriter struct { 16 | *MultiLogWriter 17 | } 18 | type MultiLogWriter struct { 19 | sync.Map 20 | } 21 | 22 | var logWriter = &LogWriter{new(MultiLogWriter)} 23 | var multiLogger = log.New(logWriter.MultiLogWriter, "", log.LstdFlags) 24 | var colorLogger = log.New(colorable.NewColorableStdout(), "", log.LstdFlags) 25 | 26 | func init() { 27 | log.SetOutput(logWriter) 28 | } 29 | func (w *LogWriter) Write(data []byte) (n int, err error) { 30 | os.Stdout.Write(data) 31 | return w.MultiLogWriter.Write(data) 32 | } 33 | func (w *MultiLogWriter) Write(data []byte) (n int, err error) { 34 | w.Range(func(k, v interface{}) bool { 35 | n, err = k.(io.Writer).Write(data) 36 | if err != nil { 37 | w.Delete(k) 38 | } 39 | return true 40 | }) 41 | return 42 | } 43 | 44 | // AddWriter 添加日志输出端 45 | func AddWriter(wn io.Writer) { 46 | logWriter.Store(wn, wn) 47 | } 48 | 49 | // MayBeError 优雅错误判断加日志辅助函数 50 | func MayBeError(info error) (hasError bool) { 51 | if hasError = info != nil; hasError { 52 | Print(aurora.Red(info)) 53 | } 54 | return 55 | } 56 | func getNoColor(v ...interface{}) (noColor []interface{}) { 57 | noColor = append(noColor, v...) 58 | for i, value := range v { 59 | if vv, ok := value.(aurora.Value); ok { 60 | noColor[i] = vv.Value() 61 | } 62 | } 63 | return 64 | } 65 | 66 | // Print 带颜色识别 67 | func Print(v ...interface{}) { 68 | noColor := getNoColor(v...) 69 | colorLogger.Output(2, fmt.Sprint(v...)) 70 | multiLogger.Output(2, fmt.Sprint(noColor...)) 71 | } 72 | 73 | // Printf calls Output to print to the standard logger. 74 | // Arguments are handled in the manner of fmt.Printf. 75 | func Printf(format string, v ...interface{}) { 76 | noColor := getNoColor(v...) 77 | colorLogger.Output(2, fmt.Sprintf(format, v...)) 78 | multiLogger.Output(2, fmt.Sprintf(format, noColor...)) 79 | } 80 | 81 | // Println calls Output to print to the standard logger. 82 | // Arguments are handled in the manner of fmt.Println. 83 | func Println(v ...interface{}) { 84 | noColor := getNoColor(v...) 85 | colorLogger.Output(2, fmt.Sprintln(v...)) 86 | multiLogger.Output(2, fmt.Sprintln(noColor...)) 87 | } 88 | -------------------------------------------------------------------------------- /runtime/media.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/logrusorgru/aurora" 10 | "github.com/micro-community/stream/app" 11 | "github.com/micro-community/stream/codecs" 12 | ) 13 | 14 | var ( 15 | streamCollection = Collection{} 16 | ) 17 | 18 | // Collection 对sync.Map的包装 19 | type Collection struct { 20 | sync.Map 21 | } 22 | 23 | // FindStream 根据流路径查找流 24 | func FindStream(streamPath string) *Stream { 25 | if s, ok := streamCollection.Load(streamPath); ok { 26 | return s.(*Stream) 27 | } 28 | return nil 29 | } 30 | 31 | // GetStream 根据流路径获取流,如果不存在则创建一个新的 32 | func GetStream(streamPath string) (result *Stream) { 33 | item, loaded := streamCollection.LoadOrStore(streamPath, &Stream{ 34 | Subscribers: make(map[string]*Subscriber), 35 | Control: make(chan interface{}), 36 | AVRing: NewRing(app.Config.RingSize), 37 | StreamInfo: StreamInfo{ 38 | StreamPath: streamPath, 39 | SubscriberInfo: make([]*SubscriberInfo, 0), 40 | HasVideo: true, 41 | HasAudio: true, 42 | EnableAudio: &app.Config.EnableAudio, 43 | EnableVideo: &app.Config.EnableVideo, 44 | }, 45 | WaitPub: make(chan struct{}), 46 | }) 47 | result = item.(*Stream) 48 | if !loaded { 49 | Summary.Streams = append(Summary.Streams, &result.StreamInfo) 50 | result.Context, result.Cancel = context.WithCancel(context.Background()) 51 | if app.Config.EnableVideo { 52 | result.EnableVideo = &result.HasVideo 53 | } 54 | if app.Config.EnableAudio { 55 | result.EnableAudio = &result.HasAudio 56 | } 57 | go result.Run() 58 | } 59 | return 60 | } 61 | 62 | // Stream 流定义 63 | type Stream struct { 64 | context.Context 65 | *Publisher 66 | StreamInfo //可序列化,供后台查看的数据 67 | Control chan interface{} 68 | Cancel context.CancelFunc 69 | Subscribers map[string]*Subscriber // 订阅者 70 | VideoTag *codecs.AVPacket // 每个视频包都是这样的结构,区别在于Payload的大小.FMS在发送AVC sequence header,需要加上 VideoTags,这个tag 1个字节(8bits)的数据 71 | AudioTag *codecs.AVPacket // 每个音频包都是这样的结构,区别在于Payload的大小.FMS在发送AAC sequence header,需要加上 AudioTags,这个tag 1个字节(8bits)的数据 72 | FirstScreen *Ring //最近的关键帧位置,首屏渲染 73 | AVRing *Ring //数据环 74 | WaitPub chan struct{} //用于订阅和等待发布者 75 | UseTimestamp bool //是否采用数据包中的时间戳 76 | SPS []byte 77 | PPS []byte 78 | } 79 | 80 | // StreamInfo 流可序列化信息,用于控制台显示 81 | type StreamInfo struct { 82 | StreamPath string 83 | StartTime time.Time 84 | SubscriberInfo []*SubscriberInfo 85 | Type string 86 | VideoInfo struct { 87 | PacketCount int 88 | CodecID byte 89 | SPSInfo codecs.SPSInfo 90 | BPS int 91 | lastIndex int 92 | GOP int //关键帧间隔 93 | } 94 | AudioInfo struct { 95 | PacketCount int 96 | SoundFormat byte //4bit 97 | SoundRate int //2bit 98 | SoundSize byte //1bit 99 | SoundType byte //1bit 100 | lastIndex int 101 | BPS int 102 | } 103 | HasAudio bool 104 | HasVideo bool 105 | EnableVideo *bool 106 | EnableAudio *bool 107 | } 108 | 109 | // UnSubscribeCmd 取消订阅命令 110 | type UnSubscribeCmd struct { 111 | *Subscriber 112 | } 113 | 114 | // SubscribeCmd 订阅流命令 115 | type SubscribeCmd struct { 116 | *Subscriber 117 | } 118 | 119 | // ChangeStreamCmd 切换流命令 120 | type ChangeStreamCmd struct { 121 | *Subscriber 122 | NewStream *Stream 123 | } 124 | 125 | func (r *Stream) onClosed() { 126 | Print(aurora.Yellow("Stream destroyed :"), aurora.BrightCyan(r.StreamPath)) 127 | streamCollection.Delete(r.StreamPath) 128 | for i, val := range Summary.Streams { 129 | if val == &r.StreamInfo { 130 | Summary.Streams = append(Summary.Streams[:i], Summary.Streams[i+1:]...) 131 | break 132 | } 133 | } 134 | OnStreamClosedHooks.Trigger(r) 135 | } 136 | 137 | // Subscribe 订阅流 138 | func (r *Stream) Subscribe(s *Subscriber) { 139 | s.Stream = r 140 | if r.Err() == nil { 141 | s.SubscribeTime = time.Now() 142 | Print(aurora.Sprintf(aurora.Yellow("subscribe :%s %s,to Stream %s"), aurora.Blue(s.Type), aurora.Cyan(s.ID), aurora.BrightCyan(r.StreamPath))) 143 | s.Context, s.Cancel = context.WithCancel(r) 144 | s.Control <- &SubscribeCmd{s} 145 | } 146 | } 147 | 148 | // UnSubscribe 取消订阅流 149 | func (r *Stream) UnSubscribe(s *Subscriber) { 150 | if r.Err() == nil { 151 | r.Control <- &UnSubscribeCmd{s} 152 | } 153 | } 154 | 155 | // Run 流运行 156 | func (r *Stream) Run() { 157 | Print(aurora.Green("Stream create:"), aurora.BrightCyan(r.StreamPath)) 158 | defer r.onClosed() 159 | for { 160 | select { 161 | case <-r.Done(): 162 | return 163 | case s := <-r.Control: 164 | switch v := s.(type) { 165 | case *UnSubscribeCmd: 166 | if _, ok := r.Subscribers[v.ID]; ok { 167 | delete(r.Subscribers, v.ID) 168 | for i, val := range r.SubscriberInfo { 169 | if val == &v.SubscriberInfo { 170 | r.SubscriberInfo = append(r.SubscriberInfo[:i], r.SubscriberInfo[i+1:]...) 171 | break 172 | } 173 | } 174 | OnUnSubscribeHooks.Trigger(v.Subscriber) 175 | Print(aurora.Sprintf(aurora.Yellow("%s subscriber %s removed remains:%d"), aurora.BrightCyan(r.StreamPath), aurora.Cyan(v.ID), aurora.Blue(len(r.SubscriberInfo)))) 176 | if len(r.SubscriberInfo) == 0 && r.Publisher == nil { 177 | r.Cancel() 178 | } 179 | } 180 | case *SubscribeCmd: 181 | //防止重复添加 182 | if _, ok := r.Subscribers[v.ID]; !ok { 183 | r.Subscribers[v.ID] = v.Subscriber 184 | r.SubscriberInfo = append(r.SubscriberInfo, &v.SubscriberInfo) 185 | Print(aurora.Sprintf(aurora.Yellow("%s subscriber %s added remains:%d"), aurora.BrightCyan(r.StreamPath), aurora.Cyan(v.ID), aurora.Blue(len(r.SubscriberInfo)))) 186 | OnSubscribeHooks.Trigger(v.Subscriber) 187 | } 188 | case *ChangeStreamCmd: 189 | if _, ok := v.NewStream.Subscribers[v.ID]; !ok { 190 | delete(r.Subscribers, v.ID) 191 | v.NewStream.Subscribe(v.Subscriber) 192 | if len(r.SubscriberInfo) == 0 && r.Publisher == nil { 193 | r.Cancel() 194 | } 195 | } 196 | } 197 | } 198 | } 199 | } 200 | 201 | // PushAudio 来自发布者推送的音频 202 | func (r *Stream) PushAudio(timestamp uint32, payload []byte) { 203 | payloadLen := len(payload) 204 | audio := r.AVRing 205 | audio.Type = codecs.FLV_TAG_TYPE_AUDIO 206 | audio.Timestamp = timestamp 207 | audio.Payload = payload 208 | audio.IsKeyFrame = false 209 | audio.IsSequence = false 210 | 211 | if payloadLen < 4 { 212 | return 213 | } 214 | if payload[0] == 0xFF && (payload[1]&0xF0) == 0xF0 { 215 | //将ADTS转换成ASC 216 | r.AudioInfo.SoundFormat = 10 217 | r.AudioInfo.SoundRate = codecs.SamplingFrequencies[(payload[2]&0x3c)>>2] 218 | r.AudioInfo.SoundType = ((payload[2] & 0x1) << 2) | ((payload[3] & 0xc0) >> 6) 219 | r.AudioTag = audio.ADTS2ASC() 220 | } else if r.AudioTag == nil { 221 | audio.IsSequence = true 222 | if payloadLen < 5 { 223 | return 224 | } 225 | r.AudioTag = audio.AVPacket.Clone() 226 | tmp := payload[0] // 第一个字节保存着音频的相关信息 227 | if r.AudioInfo.SoundFormat = tmp >> 4; r.AudioInfo.SoundFormat == 10 { //真的是AAC的话,后面有一个字节的详细信息 228 | //0 = AAC sequence header,1 = AAC raw。 229 | if aacPacketType := payload[1]; aacPacketType == 0 { 230 | config1 := payload[2] 231 | config2 := payload[3] 232 | //audioObjectType = (config1 & 0xF8) >> 3 233 | // 1 AAC MAIN ISO/IEC 14496-3 subpart 4 234 | // 2 AAC LC ISO/IEC 14496-3 subpart 4 235 | // 3 AAC SSR ISO/IEC 14496-3 subpart 4 236 | // 4 AAC LTP ISO/IEC 14496-3 subpart 4 237 | r.AudioInfo.SoundRate = codecs.SamplingFrequencies[((config1&0x7)<<1)|(config2>>7)] 238 | r.AudioInfo.SoundType = (config2 >> 3) & 0x0F //声道 239 | //frameLengthFlag = (config2 >> 2) & 0x01 240 | //dependsOnCoreCoder = (config2 >> 1) & 0x01 241 | //extensionFlag = config2 & 0x01 242 | } 243 | } else { 244 | r.AudioInfo.SoundRate = codecs.SoundRate[(tmp&0x0c)>>2] // 采样率 0 = 5.5 kHz or 1 = 11 kHz or 2 = 22 kHz or 3 = 44 kHz 245 | r.AudioInfo.SoundSize = (tmp & 0x02) >> 1 // 采样精度 0 = 8-bit samples or 1 = 16-bit samples 246 | r.AudioInfo.SoundType = tmp & 0x01 // 0 单声道,1立体声 247 | } 248 | return 249 | } 250 | if !r.UseTimestamp { 251 | audio.Timestamp = uint32(time.Since(r.StartTime) / time.Millisecond) 252 | } 253 | lastTimestamp := audio.GetAt(r.AudioInfo.lastIndex).Timestamp 254 | if lastTimestamp > 0 && lastTimestamp != audio.Timestamp { 255 | r.AudioInfo.BPS = payloadLen * 1000 / int(audio.Timestamp-lastTimestamp) 256 | } 257 | r.AudioInfo.PacketCount++ 258 | audio.Number = r.AudioInfo.PacketCount 259 | r.AudioInfo.lastIndex = audio.Index 260 | audio.NextW() 261 | } 262 | func (r *Stream) setH264Info(video *Ring) { 263 | r.VideoTag = video.AVPacket.Clone() 264 | if r.VideoInfo.CodecID != 7 { 265 | return 266 | } 267 | info := codecs.AVCDecoderConfigurationRecord{} 268 | //0:codec,1:IsAVCSequence,2~4:compositionTime 269 | if _, err := info.Unmarshal(video.Payload[5:]); err == nil { 270 | r.VideoInfo.SPSInfo, err = codecs.ParseSPS(info.SequenceParameterSetNALUnit) 271 | if err != nil { 272 | return 273 | } 274 | } 275 | } 276 | 277 | func (r *Stream) WriteSPS(sps []byte) { 278 | lenSPS := len(sps) 279 | r.SPS = sps 280 | if r.VideoTag == nil { 281 | r.VideoTag = codecs.NewAVPacket(codecs.FLV_TAG_TYPE_VIDEO) 282 | r.VideoTag.IsSequence = true 283 | r.VideoTag.IsKeyFrame = true 284 | r.VideoTag.Payload = append(r.VideoTag.Payload, codecs.RTMP_AVC_HEAD...) 285 | } 286 | r.VideoInfo.SPSInfo, _ = codecs.ParseSPS(sps) 287 | copy(r.VideoTag.Payload[6:], sps[1:4]) 288 | r.VideoTag.Payload = append(append(r.VideoTag.Payload[:10], 0xE1, byte(lenSPS>>8), byte(lenSPS)), sps...) 289 | } 290 | func (r *Stream) WritePPS(pps []byte) { 291 | lenPPS := len(pps) 292 | r.PPS = pps 293 | r.VideoTag.Payload = append(append(r.VideoTag.Payload, 0x01, byte(lenPPS>>8), byte(lenPPS)), pps...) 294 | } 295 | 296 | // PushVideo 来自发布者推送的视频 297 | func (r *Stream) PushVideo(timestamp uint32, payload []byte) { 298 | payloadLen := len(payload) 299 | if payloadLen < 3 { 300 | return 301 | } 302 | video := r.AVRing 303 | video.Type = codecs.FLV_TAG_TYPE_VIDEO 304 | video.Timestamp = timestamp 305 | video.Payload = payload 306 | videoFrameType := payload[0] >> 4 // 帧类型 4Bit, H264一般为1或者2 307 | r.VideoInfo.CodecID = payload[0] & 0x0f // 编码类型ID 4Bit, JPEG, H263, AVC... 308 | video.IsSequence = videoFrameType == 1 && payload[1] == 0 309 | video.IsKeyFrame = videoFrameType == 1 || videoFrameType == 4 310 | r.VideoInfo.PacketCount++ 311 | video.Number = r.VideoInfo.PacketCount 312 | if r.VideoTag == nil { 313 | if video.IsSequence { 314 | r.setH264Info(video) 315 | } else { 316 | log.Println("no AVCSequence") 317 | } 318 | } else { 319 | //更换AVCSequence 320 | if video.IsSequence { 321 | r.setH264Info(video) 322 | } 323 | if video.IsKeyFrame { 324 | if r.FirstScreen == nil { 325 | defer close(r.WaitPub) 326 | r.FirstScreen = video.Clone() 327 | } else { 328 | oldNumber := r.FirstScreen.Number 329 | r.FirstScreen.GoTo(video.Index) 330 | r.VideoInfo.GOP = r.FirstScreen.Number - oldNumber 331 | } 332 | } 333 | if !r.UseTimestamp { 334 | video.Timestamp = uint32(time.Since(r.StartTime) / time.Millisecond) 335 | } 336 | lastTimestamp := video.GetAt(r.VideoInfo.lastIndex).Timestamp 337 | if lastTimestamp > 0 && lastTimestamp != video.Timestamp { 338 | r.VideoInfo.BPS = payloadLen * 1000 / int(video.Timestamp-lastTimestamp) 339 | } 340 | r.VideoInfo.lastIndex = video.Index 341 | video.NextW() 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /runtime/nalu.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/micro-community/stream/codecs" 7 | ) 8 | 9 | const ( 10 | fuaHeaderSize = 2 11 | stapaHeaderSize = 1 12 | stapaNALULengthSize = 2 13 | 14 | naluTypeBitmask = 0x1F 15 | naluRefIdcBitmask = 0x60 16 | fuaStartBitmask = 0x80 //1000 0000 17 | fuaEndBitmask = 0x40 //0100 0000 18 | ) 19 | 20 | type NALU struct { 21 | Publisher 22 | fuBuffer []byte //用于fua的解析暂存的缓存 23 | lastTs uint32 24 | buffer []byte //用于存储分帧的Nalu数据 25 | } 26 | 27 | func (r *NALU) writePicture(ts uint32, head, payload []byte) { 28 | if r.VideoTag == nil { 29 | return 30 | } 31 | if payload[1]&fuaStartBitmask != 0 { 32 | if len(r.buffer) > 0 { 33 | r.PushVideo(r.lastTs, r.buffer) 34 | r.buffer = nil 35 | } 36 | r.buffer = append(r.buffer, head...) 37 | r.lastTs = ts 38 | } 39 | nl := len(payload) 40 | r.buffer = append(r.buffer, byte(nl>>24), byte(nl>>16), byte(nl>>8), byte(nl)) 41 | r.buffer = append(r.buffer, payload...) 42 | } 43 | func (r *NALU) WriteNALU(ts uint32, payload []byte) { 44 | if len(payload) == 0 { 45 | return 46 | } 47 | nalType := payload[0] & naluTypeBitmask 48 | switch nalType { 49 | case codecs.NALU_STAPA: 50 | for currOffset, naluSize := stapaHeaderSize, 0; currOffset < len(payload); currOffset += naluSize { 51 | naluSize = int(binary.BigEndian.Uint16(payload[currOffset:])) 52 | currOffset += stapaNALULengthSize 53 | if currOffset+len(payload) < currOffset+naluSize { 54 | Printf("STAP-A declared size(%d) is larger then buffer(%d)", naluSize, len(payload)-currOffset) 55 | return 56 | } 57 | r.WriteNALU(ts, payload[currOffset:currOffset+naluSize]) 58 | } 59 | case codecs.NALU_FUA: 60 | if len(payload) < fuaHeaderSize { 61 | Printf("Payload is not large enough to be FU-A") 62 | return 63 | } 64 | if payload[1]&fuaStartBitmask != 0 { 65 | naluRefIdc := payload[0] & naluRefIdcBitmask 66 | fragmentedNaluType := payload[1] & naluTypeBitmask 67 | r.fuBuffer = append([]byte{}, payload...) 68 | r.fuBuffer[fuaHeaderSize-1] = naluRefIdc | fragmentedNaluType 69 | } else { 70 | r.fuBuffer = append(r.fuBuffer, payload[fuaHeaderSize:]...) 71 | } 72 | if payload[1]&fuaEndBitmask != 0 { 73 | r.WriteNALU(ts, r.fuBuffer[fuaHeaderSize-1:]) 74 | } 75 | case codecs.NALU_SPS: 76 | r.WriteSPS(payload) 77 | case codecs.NALU_PPS: 78 | r.WritePPS(payload) 79 | case codecs.NALU_Access_Unit_Delimiter: 80 | 81 | case codecs.NALU_IDR_Picture: 82 | r.writePicture(ts, codecs.RTMP_KEYFRAME_HEAD, payload) 83 | case codecs.NALU_Non_IDR_Picture: 84 | r.writePicture(ts, codecs.RTMP_NORMALFRAME_HEAD, payload) 85 | case codecs.NALU_SEI: 86 | default: 87 | Printf("nalType not support yet:%d", nalType) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /runtime/options.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v4/config" 7 | ) 8 | 9 | // Options for micro service 10 | type Options struct { 11 | Config config.Config 12 | Context context.Context 13 | } 14 | 15 | type Option func(*Options) 16 | 17 | func newOptions(opts ...Option) Options { 18 | opt := Options{} 19 | 20 | for _, o := range opts { 21 | o(&opt) 22 | } 23 | 24 | return opt 25 | } 26 | -------------------------------------------------------------------------------- /runtime/pool/slice_pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "github.com/funny/slab" 5 | ) 6 | 7 | var ( 8 | slicePool = slab.NewChanPool( 9 | 16, // The smallest chunk size is 16B. 10 | 64*1024, // The largest chunk size is 64KB. 11 | 2, // Power of 2 growth in chunk size. 12 | 1024*1024, // Each slab will be 1MB in size. 13 | ) 14 | ) 15 | 16 | //RecycleSlice 回收 17 | func RecycleSlice(slice []byte) { 18 | slicePool.Free(slice) 19 | } 20 | 21 | //GetSlice 获取流 22 | func GetSlice(s int) []byte { 23 | return slicePool.Alloc(s) 24 | } 25 | -------------------------------------------------------------------------------- /runtime/publisher.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Publisher 发布者实体定义 8 | type Publisher struct { 9 | *Stream 10 | } 11 | 12 | // Close 关闭发布者 13 | func (p *Publisher) Close() { 14 | if p.Running() { 15 | p.Cancel() 16 | } 17 | } 18 | 19 | // Running 发布者是否正在发布 20 | func (p *Publisher) Running() bool { 21 | return p.Stream != nil && p.Err() == nil 22 | } 23 | 24 | // Publish 发布者进行发布操作 25 | func (p *Publisher) Publish(streamPath string) bool { 26 | p.Stream = GetStream(streamPath) 27 | //检查是否已存在发布者 28 | if p.Publisher != nil { 29 | return false 30 | } 31 | p.Publisher = p 32 | p.StartTime = time.Now() 33 | //触发钩子 34 | OnPublishHooks.Trigger(p.Stream) 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /runtime/ring.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | "time" 7 | 8 | "github.com/micro-community/stream/app" 9 | "github.com/micro-community/stream/codecs" 10 | ) 11 | 12 | type RingItem struct { 13 | codecs.AVPacket 14 | sync.WaitGroup 15 | *bytes.Buffer 16 | UpdateTime time.Time 17 | } 18 | 19 | // Ring 环形缓冲,使用数组实现 20 | type Ring struct { 21 | *RingItem 22 | buffer []RingItem 23 | Size int 24 | Index int 25 | } 26 | 27 | // NewRing 创建Ring,传入大小指数 28 | func NewRing(exp int) (r *Ring) { 29 | r = new(Ring) 30 | r.Size = 1 << exp 31 | r.buffer = make([]RingItem, r.Size) 32 | r.RingItem = &r.buffer[0] 33 | r.Add(1) 34 | return 35 | } 36 | func (r *Ring) offset(v int) int { 37 | return (r.Index + v) & (r.Size - 1) 38 | } 39 | 40 | // GoTo 移动到指定索引处 41 | func (r *Ring) GoTo(index int) { 42 | r.Index = index 43 | r.RingItem = &r.buffer[index] 44 | } 45 | 46 | // GetAt 获取指定索引处的引用 47 | func (r *Ring) GetAt(index int) *RingItem { 48 | return &r.buffer[index] 49 | } 50 | 51 | // GetNext 获取下一个位置的引用 52 | func (r *Ring) GetNext() *RingItem { 53 | return &r.buffer[r.offset(1)] 54 | } 55 | 56 | // GetLast 获取上一个位置的引用 57 | func (r *Ring) GetLast() *RingItem { 58 | return &r.buffer[r.offset(-1)] 59 | } 60 | 61 | // GoNext 移动到下一个位置 62 | func (r *Ring) GoNext() { 63 | r.Index = r.offset(1) 64 | r.RingItem = &r.buffer[r.Index] 65 | } 66 | 67 | // GoBack 移动到上一个位置 68 | func (r *Ring) GoBack() { 69 | r.Index = r.offset(-1) 70 | r.RingItem = &r.buffer[r.Index] 71 | } 72 | 73 | // NextW 写下一个 74 | func (r *Ring) NextW() { 75 | item := r.RingItem 76 | item.UpdateTime = time.Now() 77 | r.GoNext() 78 | r.RingItem.Add(1) 79 | item.Done() 80 | } 81 | 82 | // NextR 读下一个 83 | func (r *Ring) NextR() { 84 | r.GoNext() 85 | r.Wait() 86 | } 87 | 88 | func (r *Ring) GetBuffer() *bytes.Buffer { 89 | if r.Buffer == nil { 90 | r.Buffer = bytes.NewBuffer([]byte{}) 91 | } else { 92 | r.Reset() 93 | } 94 | return r.Buffer 95 | } 96 | 97 | // Clone 克隆一个Ring 98 | func (r Ring) Clone() *Ring { 99 | return &r 100 | } 101 | 102 | // Timeout 发布者是否超时了 103 | func (r *Ring) Timeout() bool { 104 | return time.Since(r.UpdateTime) > app.Config.PublishTimeout 105 | } 106 | -------------------------------------------------------------------------------- /runtime/runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | // Service for monibuca 4 | type Service interface { 5 | Init() error 6 | Run(configFile string) error 7 | String() string 8 | } 9 | -------------------------------------------------------------------------------- /runtime/subscriber.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/micro-community/stream/app" 8 | "github.com/micro-community/stream/codecs" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // SubscriberInfo 订阅者可序列化信息,用于控制台输出 13 | type SubscriberInfo struct { 14 | ID string 15 | TotalDrop int //总丢帧 16 | TotalPacket int 17 | Type string 18 | BufferLength int 19 | Delay uint32 20 | SubscribeTime time.Time 21 | } 22 | 23 | // Subscriber 订阅者实体定义 24 | type Subscriber struct { 25 | context.Context 26 | *Stream 27 | SubscriberInfo 28 | MetaData func(stream *Stream) error 29 | OnData func(*codecs.SendPacket) error 30 | Cancel context.CancelFunc 31 | Sign string 32 | OffsetTime uint32 33 | startTime uint32 34 | codecs.SendPacket 35 | } 36 | 37 | // IsClosed 检查订阅者是否已经关闭 38 | func (s *Subscriber) IsClosed() bool { 39 | return s.Context != nil && s.Err() != nil 40 | } 41 | 42 | // Close 关闭订阅者 43 | func (s *Subscriber) Close() { 44 | if s.Cancel != nil { 45 | s.Cancel() 46 | } 47 | } 48 | 49 | // Subscribe 开始订阅 50 | func (s *Subscriber) Subscribe(streamPath string) (err error) { 51 | if !app.Config.EnableWaitStream && FindStream(streamPath) == nil { 52 | return errors.Errorf("Stream not found:%s", streamPath) 53 | } 54 | GetStream(streamPath).Subscribe(s) 55 | if s.Context == nil { 56 | return errors.Errorf("stream not exist:%s", streamPath) 57 | } 58 | defer s.UnSubscribe(s) 59 | select { 60 | //等待发布者首屏数据,如果发布者尚为发布,则会等待,否则就会往下执行 61 | case <-s.WaitPub: 62 | case <-s.Context.Done(): 63 | return s.Err() 64 | } 65 | if s.MetaData != nil { 66 | if err = s.MetaData(s.Stream); err != nil { 67 | return err 68 | } 69 | } 70 | if *s.EnableVideo { 71 | s.sendAv(s.VideoTag, 0) 72 | packet := s.FirstScreen.Clone() 73 | s.startTime = packet.Timestamp // 开始时间戳,第一个关键帧的 74 | s.Delay = s.AVRing.GetLast().Timestamp - packet.Timestamp 75 | packet.Wait() 76 | s.send(packet) 77 | packet.NextR() 78 | // targetStartTime := s.AVRing.GetLast().Timestamp //实际开始时间戳 79 | for atsent, dropping, droped := s.AudioTag == nil, false, 0; s.Err() == nil; packet.NextR() { 80 | s.TotalPacket++ 81 | if !dropping { 82 | if !atsent && packet.Type == codecs.FLV_TAG_TYPE_AUDIO { 83 | s.sendAv(s.AudioTag, 0) 84 | atsent = true 85 | } 86 | s.sendAv(&packet.AVPacket, packet.Timestamp-s.startTime) 87 | // if targetStartTime > s.startTime { 88 | // s.startTime++ //逐步追赶,使得开始时间逼近实际开始时间戳 89 | // } 90 | if s.checkDrop(packet) { 91 | dropping = true 92 | droped = 0 93 | } 94 | } else if packet.IsKeyFrame { 95 | //遇到关键帧则退出丢帧 96 | dropping = false 97 | //fmt.Println("drop package ", droped) 98 | s.TotalDrop += droped 99 | s.send(packet) 100 | } else { 101 | droped++ 102 | } 103 | } 104 | } else if *s.EnableAudio { 105 | if s.AudioTag != nil { 106 | s.sendAv(s.AudioTag, 0) 107 | } 108 | for packet := s.AVRing; s.Err() == nil; packet.NextR() { 109 | s.TotalPacket++ 110 | s.send(packet) 111 | } 112 | } 113 | return s.Err() 114 | } 115 | 116 | func (s *Subscriber) sendAv(packet *codecs.AVPacket, t uint32) { 117 | s.AVPacket = packet 118 | s.Timestamp = t 119 | if s.OnData(&s.SendPacket) != nil { 120 | s.Close() 121 | } 122 | } 123 | func (s *Subscriber) send(packet *Ring) { 124 | 125 | s.sendAv(&packet.AVPacket, packet.Timestamp-s.startTime) 126 | } 127 | 128 | func (s *Subscriber) checkDrop(packet *Ring) bool { 129 | pIndex := s.AVRing.Index 130 | s.BufferLength = pIndex - packet.Index 131 | s.Delay = s.AVRing.Timestamp - packet.Timestamp 132 | return s.BufferLength > s.AVRing.Size/2 133 | } 134 | -------------------------------------------------------------------------------- /runtime/summary.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/shirou/gopsutil/cpu" 8 | "github.com/shirou/gopsutil/disk" 9 | "github.com/shirou/gopsutil/mem" 10 | "github.com/shirou/gopsutil/net" 11 | ) 12 | 13 | // Summary 系统摘要数据 14 | var Summary = ServerSummary{} 15 | 16 | // ServerSummary 系统摘要定义 17 | type ServerSummary struct { 18 | Address string 19 | Memory struct { 20 | Total uint64 21 | Free uint64 22 | Used uint64 23 | Usage float64 24 | } 25 | CPUUsage float64 26 | HardDisk struct { 27 | Total uint64 28 | Free uint64 29 | Used uint64 30 | Usage float64 31 | } 32 | NetWork []NetWorkInfo 33 | Streams []*StreamInfo 34 | lastNetWork []NetWorkInfo 35 | ref int 36 | control chan bool 37 | reportChan chan *ServerSummary 38 | Children map[string]*ServerSummary 39 | } 40 | 41 | // NetWorkInfo 网速信息 42 | type NetWorkInfo struct { 43 | Name string 44 | Receive uint64 45 | Sent uint64 46 | ReceiveSpeed uint64 47 | SentSpeed uint64 48 | } 49 | 50 | // StartSummary 开始定时采集数据,每秒一次 51 | func (s *ServerSummary) StartSummary() { 52 | ticker := time.NewTicker(time.Second) 53 | s.control = make(chan bool) 54 | s.reportChan = make(chan *ServerSummary) 55 | for { 56 | select { 57 | case <-ticker.C: 58 | if s.ref > 0 { 59 | Summary.collect() 60 | } 61 | case v := <-s.control: 62 | if v { 63 | if s.ref++; s.ref == 1 { 64 | log.Println("start report summary") 65 | OnSummaryHooks.Trigger(true) 66 | } 67 | } else { 68 | if s.ref--; s.ref == 0 { 69 | s.lastNetWork = nil 70 | log.Println("stop report summary") 71 | OnSummaryHooks.Trigger(false) 72 | } 73 | } 74 | case report := <-s.reportChan: 75 | s.Children[report.Address] = report 76 | } 77 | } 78 | } 79 | 80 | // Running 是否正在采集数据 81 | func (s *ServerSummary) Running() bool { 82 | return s.ref > 0 83 | } 84 | 85 | // Add 增加订阅者 86 | func (s *ServerSummary) Add() { 87 | s.control <- true 88 | } 89 | 90 | // Done 删除订阅者 91 | func (s *ServerSummary) Done() { 92 | s.control <- false 93 | } 94 | 95 | // Report 上报数据 96 | func (s *ServerSummary) Report(slave *ServerSummary) { 97 | s.reportChan <- slave 98 | } 99 | func (s *ServerSummary) collect() { 100 | v, _ := mem.VirtualMemory() 101 | //c, _ := cpu.Info() 102 | cc, _ := cpu.Percent(time.Second, false) 103 | d, _ := disk.Usage("/") 104 | //n, _ := host.Info() 105 | nv, _ := net.IOCounters(true) 106 | //boottime, _ := host.BootTime() 107 | //btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05") 108 | s.Memory.Total = v.Total / 1024 / 1024 109 | s.Memory.Free = v.Available / 1024 / 1024 110 | s.Memory.Used = v.Used / 1024 / 1024 111 | s.Memory.Usage = v.UsedPercent 112 | //fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent) 113 | //if len(c) > 1 { 114 | // for _, sub_cpu := range c { 115 | // modelname := sub_cpu.ModelName 116 | // cores := sub_cpu.Cores 117 | // fmt.Printf(" CPU : %v %v cores \n", modelname, cores) 118 | // } 119 | //} else { 120 | // sub_cpu := c[0] 121 | // modelname := sub_cpu.ModelName 122 | // cores := sub_cpu.Cores 123 | // fmt.Printf(" CPU : %v %v cores \n", modelname, cores) 124 | //} 125 | s.CPUUsage = cc[0] 126 | s.HardDisk.Free = d.Free / 1024 / 1024 / 1024 127 | s.HardDisk.Total = d.Total / 1024 / 1024 / 1024 128 | s.HardDisk.Used = d.Used / 1024 / 1024 / 1024 129 | s.HardDisk.Usage = d.UsedPercent 130 | s.NetWork = make([]NetWorkInfo, len(nv)) 131 | for i, n := range nv { 132 | s.NetWork[i].Name = n.Name 133 | s.NetWork[i].Receive = n.BytesRecv 134 | s.NetWork[i].Sent = n.BytesSent 135 | if s.lastNetWork != nil && len(s.lastNetWork) > i { 136 | s.NetWork[i].ReceiveSpeed = n.BytesRecv - s.lastNetWork[i].Receive 137 | s.NetWork[i].SentSpeed = n.BytesSent - s.lastNetWork[i].Sent 138 | } 139 | } 140 | s.lastNetWork = s.NetWork 141 | //fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent) 142 | //fmt.Printf(" SystemBoot:%v\n", btime) 143 | //fmt.Printf(" CPU Used : used %f%% \n", cc[0]) 144 | //fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent) 145 | //fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion) 146 | //fmt.Printf(" Hostname : %v \n", n.Hostname) 147 | return 148 | } 149 | -------------------------------------------------------------------------------- /service/auth/readme.md: -------------------------------------------------------------------------------- 1 | # todo 2 | -------------------------------------------------------------------------------- /service/rtp/README.md: -------------------------------------------------------------------------------- 1 | # todo 2 | -------------------------------------------------------------------------------- /service/rtsp/readme.md: -------------------------------------------------------------------------------- 1 | # todo rtsp 2 | -------------------------------------------------------------------------------- /service/webrtc/profiles.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "github.com/pion/webrtc/v3" 5 | ) 6 | 7 | // init supported default RTPCodecParameters 8 | var defaultWebrtcCodecParameters = []webrtc.RTPCodecParameters{ 9 | { 10 | RTPCodecCapability: webrtc.RTPCodecCapability{ 11 | MimeType: webrtc.MimeTypePCMU, 12 | ClockRate: 8000, 13 | Channels: 0, 14 | SDPFmtpLine: "", 15 | RTCPFeedback: nil, 16 | }, 17 | PayloadType: 0, 18 | }, 19 | { 20 | RTPCodecCapability: webrtc.RTPCodecCapability{ 21 | MimeType: webrtc.MimeTypePCMA, 22 | ClockRate: 8000, 23 | Channels: 0, 24 | SDPFmtpLine: "", 25 | RTCPFeedback: nil, 26 | }, 27 | PayloadType: 8, 28 | }, 29 | } 30 | 31 | var supportedVideoRTCPFeedback = []webrtc.RTCPFeedback{ 32 | {Type: "goog-remb", Parameter: ""}, 33 | {Type: "ccm", Parameter: "fir"}, 34 | {Type: "nack", Parameter: ""}, 35 | {Type: "nack", Parameter: "pli"}, 36 | } 37 | 38 | func RegisterCodecs(m *webrtc.MediaEngine) error { 39 | for _, codec := range defaultWebrtcCodecParameters { 40 | if err := m.RegisterCodec(codec, webrtc.RTPCodecTypeAudio); err != nil { 41 | return err 42 | } 43 | } 44 | 45 | for _, codec := range []webrtc.RTPCodecParameters{ 46 | // { 47 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, 48 | // PayloadType: 97, 49 | // }, 50 | 51 | // { 52 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil}, 53 | // PayloadType: 99, 54 | // }, 55 | 56 | // { 57 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil}, 58 | // PayloadType: 101, 59 | // }, 60 | 61 | { 62 | RTPCodecCapability: webrtc.RTPCodecCapability{ 63 | MimeType: webrtc.MimeTypeH264, 64 | ClockRate: 90000, 65 | Channels: 0, 66 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", 67 | RTCPFeedback: supportedVideoRTCPFeedback, 68 | }, 69 | PayloadType: 102, 70 | }, 71 | // { 72 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil}, 73 | // PayloadType: 121, 74 | // }, 75 | 76 | { 77 | RTPCodecCapability: webrtc.RTPCodecCapability{ 78 | MimeType: webrtc.MimeTypeH264, 79 | ClockRate: 90000, 80 | Channels: 0, 81 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", 82 | RTCPFeedback: supportedVideoRTCPFeedback, 83 | }, 84 | PayloadType: 127, 85 | }, 86 | // { 87 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, 88 | // PayloadType: 120, 89 | // }, 90 | 91 | { 92 | RTPCodecCapability: webrtc.RTPCodecCapability{ 93 | MimeType: webrtc.MimeTypeH264, 94 | ClockRate: 90000, 95 | Channels: 0, 96 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", 97 | RTCPFeedback: supportedVideoRTCPFeedback, 98 | }, 99 | PayloadType: 125, 100 | }, 101 | // { 102 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=125", nil}, 103 | // PayloadType: 107, 104 | // }, 105 | 106 | { 107 | RTPCodecCapability: webrtc.RTPCodecCapability{ 108 | MimeType: webrtc.MimeTypeH264, 109 | ClockRate: 90000, 110 | Channels: 0, 111 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", 112 | RTCPFeedback: supportedVideoRTCPFeedback, 113 | }, 114 | PayloadType: 108, 115 | }, 116 | // { 117 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil}, 118 | // PayloadType: 109, 119 | // }, 120 | 121 | { 122 | RTPCodecCapability: webrtc.RTPCodecCapability{ 123 | MimeType: webrtc.MimeTypeH264, 124 | ClockRate: 90000, 125 | Channels: 0, 126 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", 127 | RTCPFeedback: supportedVideoRTCPFeedback, 128 | }, 129 | PayloadType: 127, 130 | }, 131 | // { 132 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, 133 | // PayloadType: 120, 134 | // }, 135 | 136 | { 137 | RTPCodecCapability: webrtc.RTPCodecCapability{ 138 | MimeType: webrtc.MimeTypeH264, 139 | ClockRate: 90000, 140 | Channels: 0, 141 | SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", 142 | RTCPFeedback: supportedVideoRTCPFeedback, 143 | }, 144 | PayloadType: 123, 145 | }, 146 | // { 147 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=123", nil}, 148 | // PayloadType: 118, 149 | // }, 150 | } { 151 | if err := m.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil { 152 | return err 153 | } 154 | } 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /service/webrtc/publisher.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "github.com/micro-community/stream/pubsub" 5 | webrtcv3 "github.com/pion/webrtc/v3" 6 | ) 7 | 8 | type WebRTCPublisher struct { 9 | pubsub.Publisher 10 | WebRTCSession 11 | } 12 | 13 | func (publisher *WebRTCPublisher) OnEvent(event any) { 14 | switch event.(type) { 15 | case pubsub.IPublish: 16 | publisher.OnICEConnectionStateChange(func(connectionState webrtcv3.ICEConnectionState) { 17 | 18 | switch connectionState { 19 | case webrtcv3.ICEConnectionStateDisconnected, webrtcv3.ICEConnectionStateFailed: 20 | // publisher.Stop() 21 | } 22 | }) 23 | 24 | } 25 | // publisher.Publisher.OnEvent(event) 26 | } 27 | -------------------------------------------------------------------------------- /service/webrtc/session.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-06 09:27:38 6 | * @FilePath: \stream\service\webrtc\session.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package webrtc 11 | 12 | import ( 13 | webrtc3 "github.com/pion/webrtc/v3" 14 | ) 15 | 16 | // WebRTCSession will be changed to session 17 | type WebRTCSession struct { 18 | *webrtc3.PeerConnection 19 | SDP string 20 | } 21 | 22 | func (session *WebRTCSession) GetAnswer() (string, error) { 23 | // Sets the LocalDescription, and starts our UDP listeners 24 | answer, err := session.CreateAnswer(nil) 25 | if err != nil { 26 | return "", err 27 | } 28 | gatherComplete := webrtc3.GatheringCompletePromise(session.PeerConnection) 29 | if err := session.SetLocalDescription(answer); err != nil { 30 | return "", err 31 | } 32 | <-gatherComplete 33 | return session.LocalDescription().SDP, nil 34 | } 35 | -------------------------------------------------------------------------------- /service/webrtc/subscriber.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:00:15 6 | * @FilePath: \stream\service\webrtc\subscriber.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package webrtc 11 | 12 | import ( 13 | "github.com/micro-community/stream/pubsub" 14 | webrtc3 "github.com/pion/webrtc/v3" 15 | ) 16 | 17 | type WebRTCSubscriber struct { 18 | pubsub.Subscriber 19 | WebRTCSession 20 | videoTrack *webrtc3.TrackLocalStaticRTP 21 | audioTrack *webrtc3.TrackLocalStaticRTP 22 | } 23 | 24 | func (subscriber *WebRTCSubscriber) OnEvent(event any) { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /service/webrtc/webrtc.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "regexp" 7 | "time" 8 | 9 | "github.com/micro-community/stream/app" 10 | "github.com/pion/interceptor" 11 | webrtc3 "github.com/pion/webrtc/v3" 12 | ) 13 | 14 | var ( 15 | reg_level = regexp.MustCompile("profile-level-id=(4.+f)") 16 | // init web rtc 17 | webrtcOptions = app.WebRTCOption{ 18 | PLI: time.Second * 2, 19 | Name: "WebRTC", 20 | } 21 | ) 22 | 23 | // install webRTCPulgins 24 | var webrtcObject = app.Install(app.WithWebRTC(webrtcOptions)) 25 | 26 | // webRTC 27 | type webrtcPlugin struct { 28 | Opts app.WebRTCOption 29 | } 30 | 31 | // TODO.... 32 | type FirstConfig struct{} 33 | 34 | func (wc *webrtcPlugin) OnEvent(event any) { 35 | switch event.(type) { 36 | case FirstConfig: 37 | RegisterCodecs(&wc.Opts.ME) 38 | i := &interceptor.Registry{} 39 | if len(wc.Opts.PublicIP) > 0 { 40 | wc.Opts.SE.SetNAT1To1IPs(wc.Opts.PublicIP, webrtc3.ICECandidateTypeHost) 41 | } 42 | if wc.Opts.PortMin > 0 && wc.Opts.PortMax > 0 { 43 | wc.Opts.SE.SetEphemeralUDPPortRange(wc.Opts.PortMin, wc.Opts.PortMax) 44 | } 45 | if len(wc.Opts.PublicIP) > 0 { 46 | wc.Opts.SE.SetNAT1To1IPs(wc.Opts.PublicIP, webrtc3.ICECandidateTypeHost) 47 | } 48 | wc.Opts.SE.SetNetworkTypes([]webrtc3.NetworkType{webrtc3.NetworkTypeUDP4, webrtc3.NetworkTypeUDP6}) 49 | if err := webrtc3.RegisterDefaultInterceptors(&wc.Opts.ME, i); err != nil { 50 | panic(err) 51 | } 52 | wc.Opts.API = webrtc3.NewAPI(webrtc3.WithMediaEngine(&wc.Opts.ME), 53 | webrtc3.WithInterceptorRegistry(i), webrtc3.WithSettingEngine(wc.Opts.SE)) 54 | } 55 | } 56 | 57 | func (wc *webrtcPlugin) Play_(w http.ResponseWriter, r *http.Request) { 58 | w.Header().Set("Content-Type", "application/sdp") 59 | bytes, err := ioutil.ReadAll(r.Body) 60 | if err != nil { 61 | return 62 | } 63 | var subscriber = WebRTCSubscriber{WebRTCSession: WebRTCSession{SDP: string(bytes)}} 64 | if subscriber.PeerConnection, err = wc.Opts.API.NewPeerConnection(webrtc3.Configuration{}); err != nil { 65 | http.Error(w, err.Error(), http.StatusInternalServerError) 66 | return 67 | } 68 | subscriber.OnICECandidate(func(ice *webrtc3.ICECandidate) { 69 | if ice != nil { 70 | // suber.Info(ice.ToJSON().Candidate) 71 | } 72 | }) 73 | if err = subscriber.SetRemoteDescription(webrtc3.SessionDescription{Type: webrtc3.SDPTypeOffer, SDP: subscriber.SDP}); err != nil { 74 | http.Error(w, err.Error(), http.StatusInternalServerError) 75 | return 76 | } 77 | streamPathUrl := r.URL.Path[len("/webrtc/play/"):] 78 | if err = webrtcObject.Subscribe(streamPathUrl, &subscriber); err != nil { 79 | http.Error(w, err.Error(), http.StatusBadRequest) 80 | return 81 | } 82 | if sdp, err := subscriber.GetAnswer(); err == nil { 83 | w.Write([]byte(sdp)) 84 | } else { 85 | http.Error(w, err.Error(), http.StatusBadRequest) 86 | } 87 | } 88 | 89 | func (wc *webrtcPlugin) Push_(w http.ResponseWriter, r *http.Request) { 90 | 91 | w.Header().Set("Content-Type", "application/sdp") 92 | bytes, err := ioutil.ReadAll(r.Body) 93 | if err != nil { 94 | return 95 | } 96 | var publisher = WebRTCPublisher{WebRTCSession: WebRTCSession{SDP: string(bytes)}} 97 | if publisher.PeerConnection, err = wc.Opts.API.NewPeerConnection(webrtc3.Configuration{}); err != nil { 98 | http.Error(w, err.Error(), http.StatusInternalServerError) 99 | return 100 | } 101 | publisher.OnICECandidate(func(ice *webrtc3.ICECandidate) { 102 | if ice != nil { 103 | //puber.Info(ice.ToJSON().Candidate) 104 | } 105 | }) 106 | if _, err = publisher.AddTransceiverFromKind(webrtc3.RTPCodecTypeVideo); err != nil { 107 | http.Error(w, err.Error(), http.StatusInternalServerError) 108 | return 109 | } 110 | if _, err = publisher.AddTransceiverFromKind(webrtc3.RTPCodecTypeAudio); err != nil { 111 | http.Error(w, err.Error(), http.StatusInternalServerError) 112 | return 113 | } 114 | 115 | streamPathUrl := r.URL.Path[len("/webrtc/push/"):] 116 | if err = webrtcObject.Publish(streamPathUrl, &publisher); err != nil { 117 | http.Error(w, err.Error(), http.StatusBadRequest) 118 | return 119 | } 120 | 121 | if err := publisher.SetRemoteDescription(webrtc3.SessionDescription{Type: webrtc3.SDPTypeOffer, SDP: publisher.SDP}); err != nil { 122 | http.Error(w, err.Error(), http.StatusBadRequest) 123 | return 124 | } 125 | if answer, err := publisher.GetAnswer(); err == nil { 126 | w.Write([]byte(answer)) 127 | } else { 128 | http.Error(w, err.Error(), http.StatusBadRequest) 129 | return 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /track/audio.go: -------------------------------------------------------------------------------- 1 | package track 2 | 3 | import ( 4 | "github.com/micro-community/stream/codecs" 5 | "github.com/micro-community/stream/media" 6 | ) 7 | 8 | var adcflv1 = []byte{codecs.FLV_TAG_TYPE_AUDIO, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0} 9 | var adcflv2 = []byte{0, 0, 0, 15} 10 | 11 | // Audio for track 12 | type Audio struct { 13 | Media[media.AudioSlice] 14 | CodecID codecs.AudioCodecID 15 | Channels byte 16 | SampleSize byte 17 | AVCCHead []byte // 音频包在AVCC格式中,AAC会有两个字节,其他的只有一个字节 18 | // Profile: 19 | // 0: Main profile 20 | // 1: Low Complexity profile(LC) 21 | // 2: Scalable Sampling Rate profile(SSR) 22 | // 3: Reserved 23 | Profile byte 24 | } 25 | 26 | func (a *Audio) GetDecConfSeq() int { 27 | return a.DecoderConfiguration.Seq 28 | } 29 | -------------------------------------------------------------------------------- /track/define.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-05 17:14:18 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:41:10 6 | * @FilePath: \stream\track\define.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package track 11 | 12 | import ( 13 | "github.com/micro-community/stream/media" 14 | "github.com/micro-community/stream/util" 15 | ) 16 | 17 | // Media 基础媒体Track类 18 | type Media[T media.RawSlice] struct { 19 | media.TrackBase 20 | media.AVRing[T] 21 | SampleRate uint32 22 | DecoderConfiguration media.DecoderConfiguration[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config) 23 | // util.BytesPool //无锁内存池,用于发布者(在同一个协程中)复用小块的内存,通常是解包时需要临时使用 24 | rtpSequence uint16 //用于生成下一个rtp包的序号 25 | orderQueue []*media.RTPFrame //rtp包的缓存队列,用于乱序重排 26 | lastSeq uint16 //上一个收到的序号,用于乱序重排 27 | lastSeq2 uint16 //记录上上一个收到的序列号 28 | //流速控制 29 | } 30 | 31 | func (av *Media[T]) ReadRing() *media.AVRing[T] { 32 | return util.Clone(av.AVRing) 33 | } 34 | -------------------------------------------------------------------------------- /track/video.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-05 17:08:58 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:37:12 6 | * @FilePath: \stream\track\video.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package track 11 | 12 | import ( 13 | "github.com/micro-community/stream/algo" 14 | "github.com/micro-community/stream/codecs" 15 | "github.com/micro-community/stream/media" 16 | ) 17 | 18 | // Video for video track infos 19 | type Video struct { 20 | Media[media.NALUSlice] 21 | CodecID codecs.VideoCodecID 22 | IDRing *algo.Ring[media.AVFrame[media.NALUSlice]] `json:"-"` //最近的关键帧位置,首屏渲染 23 | SPSInfo codecs.SPSInfo 24 | GOP int //关键帧间隔 25 | naluLenSize int //avcc格式中表示nalu长度的字节数,通常为4 26 | idrCount int //缓存中包含的idr数量 27 | dcChanged bool //解码器配置是否改变了,一般由于变码率导致 28 | dtsEst *media.DTSEstimator 29 | } 30 | 31 | func (v *Video) GetDecConfSeq() int { 32 | return v.DecoderConfiguration.Seq 33 | } 34 | 35 | func (vt *Video) ReadRing() *media.AVRing[media.NALUSlice] { 36 | vr := vt.Media.ReadRing() 37 | vr.Ring = vt.IDRing 38 | return vr 39 | } 40 | -------------------------------------------------------------------------------- /util/SSE.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | "os/exec" 8 | ) 9 | 10 | var ( 11 | sseEent = []byte("event: ") 12 | sseBegin = []byte("data: ") 13 | sseEnd = []byte("\n\n") 14 | ) 15 | 16 | type SSE struct { 17 | http.ResponseWriter 18 | context.Context 19 | } 20 | 21 | func (sse *SSE) Write(data []byte) (n int, err error) { 22 | if err = sse.Err(); err != nil { 23 | return 24 | } 25 | _, err = sse.ResponseWriter.Write(sseBegin) 26 | n, err = sse.ResponseWriter.Write(data) 27 | _, err = sse.ResponseWriter.Write(sseEnd) 28 | if err != nil { 29 | return 30 | } 31 | sse.ResponseWriter.(http.Flusher).Flush() 32 | return 33 | } 34 | 35 | func (sse *SSE) WriteEvent(event string, data []byte) (err error) { 36 | if err = sse.Err(); err != nil { 37 | return 38 | } 39 | _, err = sse.ResponseWriter.Write(sseEent) 40 | _, err = sse.ResponseWriter.Write([]byte(event)) 41 | _, err = sse.ResponseWriter.Write([]byte("\n")) 42 | _, err = sse.Write(data) 43 | return 44 | } 45 | 46 | func NewSSE(w http.ResponseWriter, ctx context.Context) *SSE { 47 | header := w.Header() 48 | header.Set("Content-Type", "text/event-stream") 49 | header.Set("Cache-Control", "no-cache") 50 | header.Set("Connection", "keep-alive") 51 | header.Set("X-Accel-Buffering", "no") 52 | header.Set("Access-Control-Allow-Origin", "*") 53 | return &SSE{ 54 | w, 55 | ctx, 56 | } 57 | } 58 | 59 | func (sse *SSE) WriteJSON(data interface{}) (err error) { 60 | var jsonData []byte 61 | if jsonData, err = json.Marshal(data); err == nil { 62 | if _, err = sse.Write(jsonData); err != nil { 63 | return 64 | } 65 | return 66 | } 67 | return 68 | } 69 | func (sse *SSE) WriteExec(cmd *exec.Cmd) error { 70 | cmd.Stderr = sse 71 | cmd.Stdout = sse 72 | return cmd.Run() 73 | } 74 | -------------------------------------------------------------------------------- /util/big_little_endian.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // 4 | // 注意:RTMP模式下都是大端模式 5 | // 6 | 7 | var LittleEndian littleEndian 8 | 9 | // BigEndian is the big-endian implementation of ByteOrder. 10 | var BigEndian bigEndian 11 | 12 | // 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端. 13 | type littleEndian struct{} 14 | 15 | // b == 0x1234, b[0] == 0x12, b[1] == 0x34 16 | // b[0]低字节 b[1]高字节 17 | // 内存地址 低 -> 高 18 | // 0x34 0x12 19 | 20 | // byte(v)低字节 b[0]内存低地址 21 | // byte(v>>8)高字节 b[1]内存高地址 22 | 23 | // b == 2222 2222 1111 1111 24 | // b >> 8 -> 0000 0000 2222 2222 25 | // b << 8 -> 1111 1111 0000 0000 26 | 27 | func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } 28 | func (littleEndian) Uint24(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 } 29 | func (littleEndian) Uint32(b []byte) uint32 { 30 | return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 31 | } 32 | func (littleEndian) Uint40(b []byte) uint64 { 33 | return uint64(b[0]) | uint64(b[1])<<8 | 34 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 35 | } 36 | func (littleEndian) Uint48(b []byte) uint64 { 37 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | 38 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 39 | } 40 | func (littleEndian) Uint64(b []byte) uint64 { 41 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | 42 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 43 | } 44 | 45 | // 46 | // Put 47 | // 48 | 49 | func (littleEndian) PutUint16(b []byte, v uint16) { 50 | b[0] = byte(v) 51 | b[1] = byte(v >> 8) 52 | } 53 | func (littleEndian) PutUint24(b []byte, v uint32) { 54 | b[0] = byte(v) 55 | b[1] = byte(v >> 8) 56 | b[2] = byte(v >> 16) 57 | } 58 | func (littleEndian) PutUint32(b []byte, v uint32) { 59 | b[0] = byte(v) 60 | b[1] = byte(v >> 8) 61 | b[2] = byte(v >> 16) 62 | b[3] = byte(v >> 24) 63 | } 64 | func (littleEndian) PutUint64(b []byte, v uint64) { 65 | b[0] = byte(v) 66 | b[1] = byte(v >> 8) 67 | b[2] = byte(v >> 16) 68 | b[3] = byte(v >> 24) 69 | b[4] = byte(v >> 32) 70 | b[5] = byte(v >> 40) 71 | b[6] = byte(v >> 48) 72 | b[7] = byte(v >> 56) 73 | } 74 | 75 | // 76 | // To 77 | // 78 | 79 | func (littleEndian) ToUint16(v uint16) []byte { 80 | b := make([]byte, 2) 81 | b[0] = byte(v) 82 | b[1] = byte(v >> 8) 83 | return b 84 | } 85 | func (littleEndian) ToUint24(v uint32) []byte { 86 | b := make([]byte, 3) 87 | b[0] = byte(v) 88 | b[1] = byte(v >> 8) 89 | b[2] = byte(v >> 16) 90 | return b 91 | } 92 | func (littleEndian) ToUint32(v uint32) []byte { 93 | b := make([]byte, 4) 94 | b[0] = byte(v) 95 | b[1] = byte(v >> 8) 96 | b[2] = byte(v >> 16) 97 | b[3] = byte(v >> 24) 98 | return b 99 | } 100 | func (littleEndian) ToUint40(v uint64) []byte { 101 | b := make([]byte, 5) 102 | b[0] = byte(v) 103 | b[1] = byte(v >> 8) 104 | b[2] = byte(v >> 16) 105 | b[3] = byte(v >> 24) 106 | b[4] = byte(v >> 32) 107 | return b 108 | } 109 | func (littleEndian) ToUint48(v uint64) []byte { 110 | b := make([]byte, 6) 111 | b[0] = byte(v) 112 | b[1] = byte(v >> 8) 113 | b[2] = byte(v >> 16) 114 | b[3] = byte(v >> 24) 115 | b[4] = byte(v >> 32) 116 | b[5] = byte(v >> 40) 117 | return b 118 | } 119 | func (littleEndian) ToUint64(v uint64) []byte { 120 | b := make([]byte, 8) 121 | b[0] = byte(v) 122 | b[1] = byte(v >> 8) 123 | b[2] = byte(v >> 16) 124 | b[3] = byte(v >> 24) 125 | b[4] = byte(v >> 32) 126 | b[5] = byte(v >> 40) 127 | b[6] = byte(v >> 48) 128 | b[7] = byte(v >> 56) 129 | return b 130 | } 131 | 132 | // 高位字节排放在内存的低地址端,低位字节排放在内存的高地址端 133 | type bigEndian struct{} 134 | 135 | // b == 0x1234, b[0] == 0x12, b[1] == 0x34 136 | // 内存地址 低 -> 高 137 | // 0x12 0x34 138 | func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 } 139 | func (bigEndian) Uint24(b []byte) uint32 { return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 } 140 | func (bigEndian) Uint32(b []byte) uint32 { 141 | return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 142 | } 143 | func (bigEndian) Uint40(b []byte) uint64 { 144 | return uint64(b[4]) | uint64(b[3])<<8 | 145 | uint64(b[2])<<16 | uint64(b[1])<<24 | uint64(b[0])<<32 146 | } 147 | func (bigEndian) Uint48(b []byte) uint64 { 148 | return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | 149 | uint64(b[2])<<24 | uint64(b[1])<<32 | uint64(b[0])<<40 150 | } 151 | func (bigEndian) Uint64(b []byte) uint64 { 152 | return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | 153 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 154 | } 155 | 156 | // 157 | // Put 158 | // 159 | 160 | func (bigEndian) PutUint16(b []byte, v uint16) { 161 | b[0] = byte(v >> 8) 162 | b[1] = byte(v) 163 | } 164 | func (bigEndian) PutUint24(b []byte, v uint32) { 165 | b[0] = byte(v >> 16) 166 | b[1] = byte(v >> 8) 167 | b[2] = byte(v) 168 | } 169 | func (bigEndian) PutUint32(b []byte, v uint32) { 170 | b[0] = byte(v >> 24) 171 | b[1] = byte(v >> 16) 172 | b[2] = byte(v >> 8) 173 | b[3] = byte(v) 174 | } 175 | func (bigEndian) PutUint64(b []byte, v uint64) { 176 | b[0] = byte(v >> 56) 177 | b[1] = byte(v >> 48) 178 | b[2] = byte(v >> 40) 179 | b[3] = byte(v >> 32) 180 | b[4] = byte(v >> 24) 181 | b[5] = byte(v >> 16) 182 | b[6] = byte(v >> 8) 183 | b[7] = byte(v) 184 | } 185 | 186 | // 187 | // To 188 | // 189 | 190 | func (bigEndian) ToUint16(v uint16) []byte { 191 | b := make([]byte, 2) 192 | b[0] = byte(v >> 8) 193 | b[1] = byte(v) 194 | return b 195 | } 196 | func (bigEndian) ToUint24(v uint32) []byte { 197 | b := make([]byte, 3) 198 | b[0] = byte(v >> 16) 199 | b[1] = byte(v >> 8) 200 | b[2] = byte(v) 201 | return b 202 | } 203 | func (bigEndian) ToUint32(v uint32) []byte { 204 | b := make([]byte, 4) 205 | b[0] = byte(v >> 24) 206 | b[1] = byte(v >> 16) 207 | b[2] = byte(v >> 8) 208 | b[3] = byte(v) 209 | return b 210 | } 211 | func (bigEndian) ToUint40(v uint64) []byte { 212 | b := make([]byte, 5) 213 | b[0] = byte(v >> 32) 214 | b[1] = byte(v >> 24) 215 | b[2] = byte(v >> 16) 216 | b[3] = byte(v >> 8) 217 | b[4] = byte(v) 218 | return b 219 | } 220 | func (bigEndian) ToUint48(v uint64) []byte { 221 | b := make([]byte, 6) 222 | b[0] = byte(v >> 40) 223 | b[1] = byte(v >> 32) 224 | b[2] = byte(v >> 24) 225 | b[3] = byte(v >> 16) 226 | b[4] = byte(v >> 8) 227 | b[5] = byte(v) 228 | return b 229 | } 230 | func (bigEndian) ToUint64(v uint64) []byte { 231 | b := make([]byte, 8) 232 | b[0] = byte(v >> 56) 233 | b[1] = byte(v >> 48) 234 | b[2] = byte(v >> 40) 235 | b[3] = byte(v >> 32) 236 | b[4] = byte(v >> 24) 237 | b[5] = byte(v >> 16) 238 | b[6] = byte(v >> 8) 239 | b[7] = byte(v) 240 | return b 241 | } 242 | 243 | //哥伦布解码 244 | func GetUev(buff []byte, start int) (value int, pos int) { 245 | l := len(buff) 246 | var nZeroNum uint = 0 247 | for start < l*8 { 248 | if (buff[start/8] & (0x80 >> uint(start%8))) > 0 { 249 | break 250 | } 251 | nZeroNum += 1 252 | start += 1 253 | } 254 | dwRet := 0 255 | start += 1 256 | var i uint 257 | for i = 0; i < nZeroNum; i++ { 258 | dwRet <<= 1 259 | if (buff[start/8] & (0x80 >> uint(start%8))) > 0 { 260 | dwRet += 1 261 | } 262 | start += 1 263 | } 264 | return (1 << nZeroNum) - 1 + dwRet, start 265 | } 266 | 267 | func BigLittleSwap(v uint) uint { 268 | return (v >> 24) | ((v>>16)&0xff)<<8 | ((v>>8)&0xff)<<16 | (v&0xff)<<24 269 | } 270 | -------------------------------------------------------------------------------- /util/bits/bits.go: -------------------------------------------------------------------------------- 1 | package bits 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | //Reader for bits 8 | type Reader struct { 9 | R io.Reader 10 | n int 11 | bits uint64 12 | } 13 | 14 | //ReadBits64 read a b664 15 | func (rdr *Reader) ReadBits64(n int) (bits uint64, err error) { 16 | if rdr.n < n { 17 | var b [8]byte 18 | var got int 19 | want := (n - rdr.n + 7) / 8 20 | if got, err = rdr.R.Read(b[:want]); err != nil { 21 | return 22 | } 23 | if got < want { 24 | err = io.EOF 25 | return 26 | } 27 | for i := 0; i < got; i++ { 28 | rdr.bits <<= 8 29 | rdr.bits |= uint64(b[i]) 30 | } 31 | rdr.n += got * 8 32 | } 33 | bits = rdr.bits >> uint(rdr.n-n) 34 | rdr.bits ^= bits << uint(rdr.n-n) 35 | rdr.n -= n 36 | return 37 | } 38 | 39 | //ReadBits read a bits 40 | func (rdr *Reader) ReadBits(n int) (bits uint, err error) { 41 | var bits64 uint64 42 | if bits64, err = rdr.ReadBits64(n); err != nil { 43 | return 44 | } 45 | bits = uint(bits64) 46 | return 47 | } 48 | 49 | func (rdr *Reader) Read(p []byte) (n int, err error) { 50 | for n < len(p) { 51 | want := 8 52 | if len(p)-n < want { 53 | want = len(p) - n 54 | } 55 | var bits uint64 56 | if bits, err = rdr.ReadBits64(want * 8); err != nil { 57 | break 58 | } 59 | for i := 0; i < want; i++ { 60 | p[n+i] = byte(bits >> uint((want-i-1)*8)) 61 | } 62 | n += want 63 | } 64 | return 65 | } 66 | 67 | //Writer of bits 68 | type Writer struct { 69 | W io.Writer 70 | n int 71 | bits uint64 72 | } 73 | 74 | //WriteBits64 write a b64 75 | func (wtr *Writer) WriteBits64(bits uint64, n int) (err error) { 76 | if wtr.n+n > 64 { 77 | move := uint(64 - wtr.n) 78 | mask := bits >> move 79 | wtr.bits = (wtr.bits << move) | mask 80 | wtr.n = 64 81 | if err = wtr.FlushBits(); err != nil { 82 | return 83 | } 84 | n -= int(move) 85 | bits ^= (mask << move) 86 | } 87 | wtr.bits = (wtr.bits << uint(n)) | bits 88 | wtr.n += n 89 | return 90 | } 91 | 92 | //WriteBits write n bits 93 | func (wtr *Writer) WriteBits(bits uint, n int) (err error) { 94 | return wtr.WriteBits64(uint64(bits), n) 95 | } 96 | 97 | func (wtr *Writer) Write(p []byte) (n int, err error) { 98 | for n < len(p) { 99 | if err = wtr.WriteBits64(uint64(p[n]), 8); err != nil { 100 | return 101 | } 102 | n++ 103 | } 104 | return 105 | } 106 | 107 | //FlushBits in buffer 108 | func (wtr *Writer) FlushBits() (err error) { 109 | if wtr.n > 0 { 110 | var b [8]byte 111 | bits := wtr.bits 112 | if wtr.n%8 != 0 { 113 | bits <<= uint(8 - (wtr.n % 8)) 114 | } 115 | want := (wtr.n + 7) / 8 116 | for i := 0; i < want; i++ { 117 | b[i] = byte(bits >> uint((want-i-1)*8)) 118 | } 119 | if _, err = wtr.W.Write(b[:want]); err != nil { 120 | return 121 | } 122 | wtr.n = 0 123 | } 124 | return 125 | } 126 | 127 | //PutUInt64BE add a uint64 BE 128 | func PutUInt64BE(b []byte, res uint64, n int) { 129 | n /= 8 130 | for i := 0; i < n; i++ { 131 | b[n-i-1] = byte(res) 132 | res >>= 8 133 | } 134 | return 135 | } 136 | 137 | //PutUIntBE add a uint BE 138 | func PutUIntBE(b []byte, res uint, n int) { 139 | PutUInt64BE(b, uint64(res), n) 140 | } 141 | 142 | //WriteBytes write bytes in w 143 | func WriteBytes(w io.Writer, b []byte, n int) (err error) { 144 | if len(b) < n { 145 | b = append(b, make([]byte, n-len(b))...) 146 | } 147 | _, err = w.Write(b[:n]) 148 | return 149 | } 150 | 151 | func WriteUInt64BE(w io.Writer, val uint64, n int) (err error) { 152 | n /= 8 153 | var b [8]byte 154 | for i := n - 1; i >= 0; i-- { 155 | b[i] = byte(val) 156 | val >>= 8 157 | } 158 | return WriteBytes(w, b[:], n) 159 | } 160 | 161 | func WriteUIntBE(w io.Writer, val uint, n int) (err error) { 162 | return WriteUInt64BE(w, uint64(val), n) 163 | } 164 | 165 | func WriteInt64BE(w io.Writer, val int64, n int) (err error) { 166 | n /= 8 167 | var uval uint 168 | if val < 0 { 169 | uval = uint((1 << uint(n*8)) + val) 170 | } else { 171 | uval = uint(val) 172 | } 173 | return WriteUIntBE(w, uval, n) 174 | } 175 | 176 | func WriteIntBE(w io.Writer, val int, n int) (err error) { 177 | return WriteInt64BE(w, int64(val), n) 178 | } 179 | 180 | func WriteString(w io.Writer, val string, n int) (err error) { 181 | return WriteBytes(w, []byte(val), n) 182 | } 183 | -------------------------------------------------------------------------------- /util/bits/bits_test.go: -------------------------------------------------------------------------------- 1 | package bits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestBits(t *testing.T) { 9 | rdata := []byte{0xf3, 0xb3, 0x45, 0x60} 10 | rbuf := bytes.NewReader(rdata[:]) 11 | r := &Reader{R: rbuf} 12 | var u32 uint 13 | if u32, _ = r.ReadBits(4); u32 != 0xf { 14 | t.FailNow() 15 | } 16 | if u32, _ = r.ReadBits(4); u32 != 0x3 { 17 | t.FailNow() 18 | } 19 | if u32, _ = r.ReadBits(2); u32 != 0x2 { 20 | t.FailNow() 21 | } 22 | if u32, _ = r.ReadBits(2); u32 != 0x3 { 23 | t.FailNow() 24 | } 25 | b := make([]byte, 2) 26 | if r.Read(b); b[0] != 0x34 || b[1] != 0x56 { 27 | t.FailNow() 28 | } 29 | 30 | wbuf := &bytes.Buffer{} 31 | w := &Writer{W: wbuf} 32 | w.WriteBits(0xf, 4) 33 | w.WriteBits(0x3, 4) 34 | w.WriteBits(0x2, 2) 35 | w.WriteBits(0x3, 2) 36 | n, _ := w.Write([]byte{0x34, 0x56}) 37 | if n != 2 { 38 | t.FailNow() 39 | } 40 | w.FlushBits() 41 | wdata := wbuf.Bytes() 42 | if wdata[0] != 0xf3 || wdata[1] != 0xb3 || wdata[2] != 0x45 || wdata[3] != 0x60 { 43 | t.FailNow() 44 | } 45 | 46 | b = make([]byte, 8) 47 | PutUInt64BE(b, 0x11223344, 32) 48 | if b[0] != 0x11 || b[1] != 0x22 || b[2] != 0x33 || b[3] != 0x44 { 49 | t.FailNow() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /util/bits/bufio/bufio.go: -------------------------------------------------------------------------------- 1 | package bufio 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Reader struct { 8 | buf [][]byte 9 | R io.ReadSeeker 10 | } 11 | 12 | func NewReaderSize(r io.ReadSeeker, size int) *Reader { 13 | buf := make([]byte, size*2) 14 | return &Reader{ 15 | R: r, 16 | buf: [][]byte{buf[0:size], buf[size:]}, 17 | } 18 | } 19 | 20 | func (self *Reader) ReadAt(b []byte, off int64) (n int, err error) { 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /util/bits/golomb_reader.go: -------------------------------------------------------------------------------- 1 | package bits 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type GolombBitReader struct { 8 | R io.Reader 9 | buf [1]byte 10 | left byte 11 | } 12 | 13 | func (self *GolombBitReader) ReadBit() (res uint, err error) { 14 | if self.left == 0 { 15 | if _, err = self.R.Read(self.buf[:]); err != nil { 16 | return 17 | } 18 | self.left = 8 19 | } 20 | self.left-- 21 | res = uint(self.buf[0]>>self.left) & 1 22 | return 23 | } 24 | 25 | func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { 26 | for i := 0; i < n; i++ { 27 | var bit uint 28 | if bit, err = self.ReadBit(); err != nil { 29 | return 30 | } 31 | res |= bit << uint(n-i-1) 32 | } 33 | return 34 | } 35 | 36 | func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) { 37 | i := 0 38 | for { 39 | var bit uint 40 | if bit, err = self.ReadBit(); err != nil { 41 | return 42 | } 43 | if !(bit == 0 && i < 32) { 44 | break 45 | } 46 | i++ 47 | } 48 | if res, err = self.ReadBits(i); err != nil { 49 | return 50 | } 51 | res += (1 << uint(i)) - 1 52 | return 53 | } 54 | 55 | func (self *GolombBitReader) ReadSE() (res uint, err error) { 56 | if res, err = self.ReadExponentialGolombCode(); err != nil { 57 | return 58 | } 59 | if res&0x01 != 0 { 60 | res = (res + 1) / 2 61 | } else { 62 | res = -res / 2 63 | } 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /util/bits/pio/pio.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | var RecommendBufioSize = 1024 * 64 4 | -------------------------------------------------------------------------------- /util/bits/pio/reader.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | func U8(b []byte) (i uint8) { 4 | return b[0] 5 | } 6 | 7 | func U16BE(b []byte) (i uint16) { 8 | i = uint16(b[0]) 9 | i <<= 8 10 | i |= uint16(b[1]) 11 | return 12 | } 13 | 14 | func I16BE(b []byte) (i int16) { 15 | i = int16(b[0]) 16 | i <<= 8 17 | i |= int16(b[1]) 18 | return 19 | } 20 | 21 | func I24BE(b []byte) (i int32) { 22 | i = int32(int8(b[0])) 23 | i <<= 8 24 | i |= int32(b[1]) 25 | i <<= 8 26 | i |= int32(b[2]) 27 | return 28 | } 29 | 30 | func U24BE(b []byte) (i uint32) { 31 | i = uint32(b[0]) 32 | i <<= 8 33 | i |= uint32(b[1]) 34 | i <<= 8 35 | i |= uint32(b[2]) 36 | return 37 | } 38 | 39 | func I32BE(b []byte) (i int32) { 40 | i = int32(int8(b[0])) 41 | i <<= 8 42 | i |= int32(b[1]) 43 | i <<= 8 44 | i |= int32(b[2]) 45 | i <<= 8 46 | i |= int32(b[3]) 47 | return 48 | } 49 | 50 | func U32LE(b []byte) (i uint32) { 51 | i = uint32(b[3]) 52 | i <<= 8 53 | i |= uint32(b[2]) 54 | i <<= 8 55 | i |= uint32(b[1]) 56 | i <<= 8 57 | i |= uint32(b[0]) 58 | return 59 | } 60 | 61 | func U32BE(b []byte) (i uint32) { 62 | i = uint32(b[0]) 63 | i <<= 8 64 | i |= uint32(b[1]) 65 | i <<= 8 66 | i |= uint32(b[2]) 67 | i <<= 8 68 | i |= uint32(b[3]) 69 | return 70 | } 71 | 72 | func U40BE(b []byte) (i uint64) { 73 | i = uint64(b[0]) 74 | i <<= 8 75 | i |= uint64(b[1]) 76 | i <<= 8 77 | i |= uint64(b[2]) 78 | i <<= 8 79 | i |= uint64(b[3]) 80 | i <<= 8 81 | i |= uint64(b[4]) 82 | return 83 | } 84 | 85 | func U64BE(b []byte) (i uint64) { 86 | i = uint64(b[0]) 87 | i <<= 8 88 | i |= uint64(b[1]) 89 | i <<= 8 90 | i |= uint64(b[2]) 91 | i <<= 8 92 | i |= uint64(b[3]) 93 | i <<= 8 94 | i |= uint64(b[4]) 95 | i <<= 8 96 | i |= uint64(b[5]) 97 | i <<= 8 98 | i |= uint64(b[6]) 99 | i <<= 8 100 | i |= uint64(b[7]) 101 | return 102 | } 103 | 104 | func I64BE(b []byte) (i int64) { 105 | i = int64(int8(b[0])) 106 | i <<= 8 107 | i |= int64(b[1]) 108 | i <<= 8 109 | i |= int64(b[2]) 110 | i <<= 8 111 | i |= int64(b[3]) 112 | i <<= 8 113 | i |= int64(b[4]) 114 | i <<= 8 115 | i |= int64(b[5]) 116 | i <<= 8 117 | i |= int64(b[6]) 118 | i <<= 8 119 | i |= int64(b[7]) 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /util/bits/pio/vec.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | func VecLen(vec [][]byte) (n int) { 4 | for _, b := range vec { 5 | n += len(b) 6 | } 7 | return 8 | } 9 | 10 | func VecSliceTo(in [][]byte, out [][]byte, s int, e int) (n int) { 11 | if s < 0 { 12 | s = 0 13 | } 14 | 15 | if e >= 0 && e < s { 16 | panic("pio: VecSlice start > end") 17 | } 18 | 19 | i := 0 20 | off := 0 21 | for s > 0 && i < len(in) { 22 | left := len(in[i]) 23 | read := s 24 | if left < read { 25 | read = left 26 | } 27 | left -= read 28 | off += read 29 | s -= read 30 | e -= read 31 | if left == 0 { 32 | i++ 33 | off = 0 34 | } 35 | } 36 | if s > 0 { 37 | panic("pio: VecSlice start out of range") 38 | } 39 | 40 | for e != 0 && i < len(in) { 41 | left := len(in[i]) - off 42 | read := left 43 | if e > 0 && e < read { 44 | read = e 45 | } 46 | out[n] = in[i][off : off+read] 47 | n++ 48 | left -= read 49 | e -= read 50 | off += read 51 | if left == 0 { 52 | i++ 53 | off = 0 54 | } 55 | } 56 | if e > 0 { 57 | panic("pio: VecSlice end out of range") 58 | } 59 | 60 | return 61 | } 62 | 63 | func VecSlice(in [][]byte, s int, e int) (out [][]byte) { 64 | out = make([][]byte, len(in)) 65 | n := VecSliceTo(in, out, s, e) 66 | out = out[:n] 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /util/bits/pio/vec_test.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func TestExampleVec() { 8 | vec := [][]byte{{1, 2, 3}, {4, 5, 6, 7, 8, 9}, {10, 11, 12, 13}} 9 | println(VecLen(vec)) 10 | 11 | vec = VecSlice(vec, 1, -1) 12 | fmt.Println(vec) 13 | 14 | vec = VecSlice(vec, 2, -1) 15 | fmt.Println(vec) 16 | 17 | vec = VecSlice(vec, 8, 8) 18 | fmt.Println(vec) 19 | 20 | // Output: 21 | } 22 | -------------------------------------------------------------------------------- /util/bits/pio/writer.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | func PutU8(b []byte, v uint8) { 4 | b[0] = v 5 | } 6 | 7 | func PutI16BE(b []byte, v int16) { 8 | b[0] = byte(v >> 8) 9 | b[1] = byte(v) 10 | } 11 | 12 | func PutU16BE(b []byte, v uint16) { 13 | b[0] = byte(v >> 8) 14 | b[1] = byte(v) 15 | } 16 | 17 | func PutI24BE(b []byte, v int32) { 18 | b[0] = byte(v >> 16) 19 | b[1] = byte(v >> 8) 20 | b[2] = byte(v) 21 | } 22 | 23 | func PutU24BE(b []byte, v uint32) { 24 | b[0] = byte(v >> 16) 25 | b[1] = byte(v >> 8) 26 | b[2] = byte(v) 27 | } 28 | 29 | func PutI32BE(b []byte, v int32) { 30 | b[0] = byte(v >> 24) 31 | b[1] = byte(v >> 16) 32 | b[2] = byte(v >> 8) 33 | b[3] = byte(v) 34 | } 35 | 36 | func PutU32BE(b []byte, v uint32) { 37 | b[0] = byte(v >> 24) 38 | b[1] = byte(v >> 16) 39 | b[2] = byte(v >> 8) 40 | b[3] = byte(v) 41 | } 42 | 43 | func PutU32LE(b []byte, v uint32) { 44 | b[3] = byte(v >> 24) 45 | b[2] = byte(v >> 16) 46 | b[1] = byte(v >> 8) 47 | b[0] = byte(v) 48 | } 49 | 50 | func PutU40BE(b []byte, v uint64) { 51 | b[0] = byte(v >> 32) 52 | b[1] = byte(v >> 24) 53 | b[2] = byte(v >> 16) 54 | b[3] = byte(v >> 8) 55 | b[4] = byte(v) 56 | } 57 | 58 | func PutU48BE(b []byte, v uint64) { 59 | b[0] = byte(v >> 40) 60 | b[1] = byte(v >> 32) 61 | b[2] = byte(v >> 24) 62 | b[3] = byte(v >> 16) 63 | b[4] = byte(v >> 8) 64 | b[5] = byte(v) 65 | } 66 | 67 | func PutU64BE(b []byte, v uint64) { 68 | b[0] = byte(v >> 56) 69 | b[1] = byte(v >> 48) 70 | b[2] = byte(v >> 40) 71 | b[3] = byte(v >> 32) 72 | b[4] = byte(v >> 24) 73 | b[5] = byte(v >> 16) 74 | b[6] = byte(v >> 8) 75 | b[7] = byte(v) 76 | } 77 | 78 | func PutI64BE(b []byte, v int64) { 79 | b[0] = byte(v >> 56) 80 | b[1] = byte(v >> 48) 81 | b[2] = byte(v >> 40) 82 | b[3] = byte(v >> 32) 83 | b[4] = byte(v >> 24) 84 | b[5] = byte(v >> 16) 85 | b[6] = byte(v >> 8) 86 | b[7] = byte(v) 87 | } 88 | -------------------------------------------------------------------------------- /util/convert.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | /* 9 | func ReadByteToUintX(r io.Reader, l int) (data uint64, err error) { 10 | if l%8 != 0 || l > 64 { 11 | return 0, errors.New("disable convert") 12 | } 13 | 14 | bb := make([]byte, l) 15 | if _, err := io.ReadFull(r, bb); err != nil { 16 | return 0, err 17 | } 18 | 19 | switch l / 8 { 20 | case 1: 21 | { 22 | return uint8(bb[0]), nil 23 | } 24 | case 2: 25 | { 26 | return BigEndian.Uint16(bb), nil 27 | } 28 | case 3: 29 | { 30 | return BigEndian.Uint24(bb), nil 31 | } 32 | case 4: 33 | { 34 | return BigEndian.Uint32(bb), nil 35 | } 36 | case 5: 37 | { 38 | //return BigEndian.Uint40(bb), nil 39 | return 0, errors.New("disable convert") 40 | } 41 | case 6: 42 | { 43 | return BigEndian.Uint48(bb), nil 44 | } 45 | case 7: 46 | { 47 | //return BigEndian.Uint56(bb), nil 48 | return 0, errors.New("disable convert") 49 | } 50 | case 8: 51 | { 52 | return BigEndian.Uint64(bb), nil 53 | } 54 | } 55 | 56 | return 0, errors.New("convert not exist") 57 | } 58 | */ 59 | 60 | // // 千万注意大小端,RTMP是大端 61 | func ByteToUint32N(data []byte) (ret uint32, err error) { 62 | if len(data) > 4 { 63 | return 0, errors.New("ByteToUint32N error!") 64 | } 65 | 66 | for i := 0; i < len(data); i++ { 67 | ret <<= 8 68 | ret |= uint32(data[i]) 69 | } 70 | 71 | return 72 | } 73 | 74 | // // 千万注意大小端,RTMP是大端 75 | func ByteToUint64N(data []byte) (ret uint64, err error) { 76 | if len(data) > 8 { 77 | return 0, errors.New("ByteToUint64N error!") 78 | } 79 | 80 | for i := 0; i < len(data); i++ { 81 | ret <<= 8 82 | ret |= uint64(data[i]) 83 | } 84 | 85 | return 86 | } 87 | 88 | // 千万注意大小端,RTMP是大端 89 | func ByteToUint32(data []byte, bigEndian bool) (ret uint32, err error) { 90 | if bigEndian { 91 | return BigEndian.Uint32(data), nil 92 | } else { 93 | return LittleEndian.Uint32(data), nil 94 | } 95 | } 96 | 97 | func Uint32ToByte(data uint32, bigEndian bool) (ret []byte, err error) { 98 | if bigEndian { 99 | return BigEndian.ToUint32(data), nil 100 | } else { 101 | return LittleEndian.ToUint32(data), nil 102 | } 103 | } 104 | 105 | func ReadByteToUint8(r io.Reader) (data uint8, err error) { 106 | bb := make([]byte, 1) 107 | if _, err := io.ReadFull(r, bb); err != nil { 108 | return 0, err 109 | } 110 | 111 | return uint8(bb[0]), nil 112 | } 113 | 114 | func ReadByteToUint16(r io.Reader, bigEndian bool) (data uint16, err error) { 115 | bb := make([]byte, 2) 116 | if _, err := io.ReadFull(r, bb); err != nil { 117 | return 0, err 118 | } 119 | 120 | if bigEndian { 121 | return BigEndian.Uint16(bb), nil 122 | } else { 123 | return LittleEndian.Uint16(bb), nil 124 | } 125 | } 126 | 127 | func ReadByteToUint24(r io.Reader, bigEndian bool) (data uint32, err error) { 128 | bb := make([]byte, 3) 129 | if _, err := io.ReadFull(r, bb); err != nil { 130 | return 0, err 131 | } 132 | 133 | if bigEndian { 134 | return BigEndian.Uint24(bb), nil 135 | } else { 136 | return LittleEndian.Uint24(bb), nil 137 | } 138 | } 139 | 140 | func ReadByteToUint32(r io.Reader, bigEndian bool) (data uint32, err error) { 141 | bb := make([]byte, 4) 142 | if _, err := io.ReadFull(r, bb); err != nil { 143 | return 0, err 144 | } 145 | 146 | if bigEndian { 147 | return BigEndian.Uint32(bb), nil 148 | } else { 149 | return LittleEndian.Uint32(bb), nil 150 | } 151 | } 152 | 153 | func ReadByteToUint40(r io.Reader, bigEndian bool) (data uint64, err error) { 154 | bb := make([]byte, 5) 155 | if _, err := io.ReadFull(r, bb); err != nil { 156 | return 0, err 157 | } 158 | 159 | if bigEndian { 160 | return BigEndian.Uint40(bb), nil 161 | } else { 162 | return LittleEndian.Uint40(bb), nil 163 | } 164 | } 165 | 166 | func ReadByteToUint48(r io.Reader, bigEndian bool) (data uint64, err error) { 167 | bb := make([]byte, 6) 168 | if _, err := io.ReadFull(r, bb); err != nil { 169 | return 0, err 170 | } 171 | 172 | if bigEndian { 173 | return BigEndian.Uint48(bb), nil 174 | } else { 175 | return LittleEndian.Uint48(bb), nil 176 | } 177 | } 178 | 179 | /* 180 | func ReadByteToUint56(r io.Reader) (data uint64, err error) { 181 | bb := make([]byte, 7) 182 | if _, err := io.ReadFull(r, bb); err != nil { 183 | return 0, err 184 | } 185 | 186 | return uint8(bb[0]), nil 187 | } 188 | */ 189 | 190 | func ReadByteToUint64(r io.Reader, bigEndian bool) (data uint64, err error) { 191 | bb := make([]byte, 8) 192 | if _, err := io.ReadFull(r, bb); err != nil { 193 | return 0, err 194 | } 195 | 196 | if bigEndian { 197 | return BigEndian.Uint64(bb), nil 198 | } else { 199 | return LittleEndian.Uint64(bb), nil 200 | } 201 | } 202 | 203 | func WriteUint8ToByte(w io.Writer, data uint8) error { 204 | bb := make([]byte, 8) 205 | bb[0] = byte(data) 206 | _, err := w.Write(bb[:1]) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | return nil 212 | } 213 | 214 | func WriteUint16ToByte(w io.Writer, data uint16, bigEndian bool) error { 215 | var bb []byte 216 | if bigEndian { 217 | bb = BigEndian.ToUint16(data) 218 | } else { 219 | bb = LittleEndian.ToUint16(data) 220 | } 221 | 222 | _, err := w.Write(bb) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | return nil 228 | } 229 | 230 | func WriteUint24ToByte(w io.Writer, data uint32, bigEndian bool) error { 231 | var bb []byte 232 | if bigEndian { 233 | bb = BigEndian.ToUint24(data) 234 | } else { 235 | bb = LittleEndian.ToUint24(data) 236 | } 237 | 238 | _, err := w.Write(bb) 239 | if err != nil { 240 | return err 241 | } 242 | 243 | return nil 244 | } 245 | 246 | func WriteUint32ToByte(w io.Writer, data uint32, bigEndian bool) error { 247 | var bb []byte 248 | if bigEndian { 249 | bb = BigEndian.ToUint32(data) 250 | } else { 251 | bb = LittleEndian.ToUint32(data) 252 | } 253 | 254 | _, err := w.Write(bb) 255 | if err != nil { 256 | return err 257 | } 258 | 259 | return nil 260 | } 261 | 262 | func WriteUint40ToByte(w io.Writer, data uint64, bigEndian bool) error { 263 | var bb []byte 264 | if bigEndian { 265 | bb = BigEndian.ToUint40(data) 266 | } else { 267 | bb = LittleEndian.ToUint40(data) 268 | } 269 | 270 | _, err := w.Write(bb) 271 | if err != nil { 272 | return err 273 | } 274 | 275 | return nil 276 | } 277 | 278 | func WriteUint48ToByte(w io.Writer, data uint64, bigEndian bool) error { 279 | var bb []byte 280 | if bigEndian { 281 | bb = BigEndian.ToUint48(data) 282 | } else { 283 | bb = LittleEndian.ToUint48(data) 284 | } 285 | 286 | _, err := w.Write(bb) 287 | if err != nil { 288 | return err 289 | } 290 | 291 | return nil 292 | } 293 | 294 | func WriteUint64ToByte(w io.Writer, data uint64, bigEndian bool) error { 295 | var bb []byte 296 | if bigEndian { 297 | bb = BigEndian.ToUint64(data) 298 | } else { 299 | bb = LittleEndian.ToUint64(data) 300 | } 301 | 302 | _, err := w.Write(bb) 303 | if err != nil { 304 | return err 305 | } 306 | 307 | return nil 308 | } 309 | 310 | func GetPtsDts(v uint64) uint64 { 311 | // 4 + 3 + 1 + 15 + 1 + 15 + 1 312 | // 0011 313 | // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit 314 | pts1 := ((v >> 33) & 0x7) << 30 315 | pts2 := ((v >> 17) & 0x7fff) << 15 316 | pts3 := ((v >> 1) & 0x7fff) 317 | 318 | return pts1 | pts2 | pts3 319 | } 320 | 321 | func PutPtsDts(v uint64) uint64 { 322 | // 4 + 3 + 1 + 15 + 1 + 15 + 1 323 | // 0011 324 | // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit 325 | // 0x100010001 326 | // 0001 0000 0000 0000 0001 0000 0000 0000 0001 327 | // 3个 market_it 328 | pts1 := (v >> 30) & 0x7 << 33 329 | pts2 := (v >> 15) & 0x7fff << 17 330 | pts3 := (v & 0x7fff) << 1 331 | 332 | return pts1 | pts2 | pts3 | 0x100010001 333 | } 334 | 335 | func GetPCR(v uint64) uint64 { 336 | // program_clock_reference_base(33) + Reserved(6) + program_clock_reference_extension(9) 337 | base := v >> 15 338 | ext := v & 0x1ff 339 | return base*300 + ext 340 | } 341 | 342 | func PutPCR(pcr uint64) uint64 { 343 | base := pcr / 300 344 | ext := pcr % 300 345 | return base<<15 | 0x3f<<9 | ext 346 | } 347 | 348 | func GetFillBytes(data byte, n int) []byte { 349 | b := make([]byte, n) 350 | for i := range b { 351 | b[i] = data 352 | } 353 | 354 | return b 355 | } 356 | func ToFloat64(num interface{}) float64 { 357 | switch v := num.(type) { 358 | case uint: 359 | return float64(v) 360 | case int: 361 | return float64(v) 362 | case uint8: 363 | return float64(v) 364 | case uint16: 365 | return float64(v) 366 | case uint32: 367 | return float64(v) 368 | case uint64: 369 | return float64(v) 370 | case int8: 371 | return float64(v) 372 | case int16: 373 | return float64(v) 374 | case int32: 375 | return float64(v) 376 | case int64: 377 | return float64(v) 378 | case float64: 379 | return v 380 | case float32: 381 | return float64(v) 382 | } 383 | return 0 384 | } 385 | -------------------------------------------------------------------------------- /util/crc32.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | ) 8 | 9 | //Crc32Table for crc32 computing 10 | var Crc32Table = []uint32{ 11 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 12 | 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 13 | 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 14 | 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 15 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 16 | 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 17 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 18 | 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 19 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 20 | 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 21 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 22 | 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 23 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 24 | 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 25 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 26 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 27 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 28 | 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 29 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 30 | 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 31 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 32 | 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 33 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 34 | 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 35 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 36 | 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 37 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 38 | 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 39 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 40 | 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 41 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 42 | 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 43 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 44 | 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 45 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 46 | 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 47 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 48 | 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 49 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 50 | 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 51 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 52 | 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 53 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 54 | 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 55 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 56 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 57 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 58 | 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 59 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 60 | 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 61 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 62 | 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 63 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 64 | 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 65 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 66 | 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 67 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 68 | 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 69 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 70 | 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 71 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 72 | 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 73 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 74 | 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, 75 | } 76 | 77 | type Crc32Reader struct { 78 | R io.Reader 79 | Crc32 uint32 80 | } 81 | 82 | type Crc32Writer struct { 83 | W io.Writer 84 | Crc32 uint32 85 | } 86 | 87 | func (cr *Crc32Reader) Read(b []byte) (n int, err error) { 88 | if n, err = cr.R.Read(b); err != nil { 89 | return 90 | } 91 | 92 | cr.Crc32 = getCrc32(cr.Crc32, b) 93 | 94 | return 95 | } 96 | 97 | func (cr *Crc32Reader) ReadCrc32UIntAndCheck() (err error) { 98 | _, err = io.CopyN(ioutil.Discard, cr, 4) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | if cr.Crc32 != 0 { 104 | err = fmt.Errorf("crc32(%x) != 0", cr.Crc32) 105 | return err 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func (wr *Crc32Writer) Write(b []byte) (n int, err error) { 112 | if n, err = wr.W.Write(b); err != nil { 113 | return 114 | } 115 | 116 | wr.Crc32 = getCrc32(wr.Crc32, b) 117 | 118 | return 119 | } 120 | 121 | func getCrc32(crc uint32, data []byte) uint32 { 122 | for _, v := range data { 123 | crc = Crc32Table[v^byte(crc)] ^ (crc >> 8) 124 | } 125 | 126 | return crc 127 | } 128 | -------------------------------------------------------------------------------- /util/stderr.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package util 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | func init() { 12 | logFile, err := os.OpenFile("./fatal.log", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) 13 | if err != nil { 14 | log.Println("服务启动出错", "打开异常日志文件失败", err) 15 | return 16 | } 17 | 18 | // 将进程标准出错重定向至文件,进程崩溃时运行时将向该文件记录协程调用栈信息 19 | syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd())) 20 | } 21 | -------------------------------------------------------------------------------- /util/sys.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Edward crazybber@outlook.com 3 | * @Date: 2022-09-02 12:47:33 4 | * @LastEditors: Edward crazybber@outlook.com 5 | * @LastEditTime: 2022-09-05 17:40:19 6 | * @FilePath: \stream\util\sys.go 7 | * @Description: code content 8 | * Copyright (c) 2022 by Edward crazybber@outlook.com, All Rights Reserved. 9 | */ 10 | package util 11 | 12 | import ( 13 | "bufio" 14 | "context" 15 | "io" 16 | "os" 17 | "os/signal" 18 | "path/filepath" 19 | "runtime" 20 | "syscall" 21 | ) 22 | 23 | func Clone[T any](x T) *T { 24 | return &x 25 | } 26 | 27 | // Exist check file or dir exist 28 | func Exist(filename string) bool { 29 | _, err := os.Stat(filename) 30 | return err == nil || os.IsExist(err) 31 | } 32 | 33 | // ReadFileLines read by line 34 | func ReadFileLines(filename string) (lines []string, err error) { 35 | file, err := os.OpenFile(filename, os.O_RDONLY, 0644) 36 | if err != nil { 37 | return 38 | } 39 | defer file.Close() 40 | 41 | bio := bufio.NewReader(file) 42 | for { 43 | var line []byte 44 | 45 | line, _, err = bio.ReadLine() 46 | if err != nil { 47 | if err == io.EOF { 48 | file.Close() 49 | return lines, nil 50 | } 51 | return 52 | } 53 | 54 | lines = append(lines, string(line)) 55 | } 56 | 57 | } 58 | 59 | // CurrentDir for working directory 60 | func CurrentDir(path ...string) string { 61 | _, currentFilePath, _, _ := runtime.Caller(1) 62 | if len(path) == 0 { 63 | return filepath.Dir(currentFilePath) 64 | } 65 | return filepath.Join(filepath.Dir(currentFilePath), filepath.Join(path...)) 66 | } 67 | 68 | func WaitTerm(cancel context.CancelFunc) { 69 | sig := make(chan os.Signal, 1) 70 | signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) 71 | defer signal.Stop(sig) 72 | <-sig 73 | cancel() 74 | } 75 | -------------------------------------------------------------------------------- /util/vecio.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | 5 | //"fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "unsafe" 10 | ) 11 | 12 | /* 13 | #include 14 | // Structure for scatter/gather I/O. 15 | struct iovec{ 16 | void *iov_base; // Pointer to data. 17 | size_t iov_len; // Length of data. 18 | }; 19 | */ 20 | 21 | type SysIOVec struct { 22 | Base uintptr 23 | Length uint64 24 | } 25 | 26 | type IOVec struct { 27 | Data [][]byte 28 | Length int 29 | index int 30 | } 31 | 32 | func (iov *IOVec) Append(b []byte) { 33 | iov.Data = append(iov.Data, b) 34 | iov.Length += len(b) 35 | } 36 | 37 | // Data模型: 38 | // index -> | Data[0][0] | Data[0][1] | Data[0][2] | ... | Data[0][n] | 39 | // | Data[1][0] | Data[1][1] | Data[1][2] | ... | Data[1][n] | 40 | // ...... 41 | // | Data[n][0] | Data[n][1] | Data[n][2] | ... | Data[n][n] | 42 | // 43 | // index是下标 44 | 45 | func (iov *IOVec) WriteTo(w io.Writer, n int) (written int, err error) { 46 | for n > 0 && iov.Length > 0 { 47 | data := iov.Data[iov.index] 48 | 49 | // 用来存放每次需要写入的数据 50 | var b []byte 51 | 52 | // 只会读n个字节,超出n个字节,不管 53 | // 如果iov.Data里面有1000个数据,可是每次只读184个字节,那么剩下的数据(856)重新放回Data 54 | if n > len(data) { 55 | b = data 56 | } else { 57 | b = data[:n] 58 | } 59 | 60 | // n个字节后面的数据 61 | // 如果这时候n个字节后面已经没有数据了,我们就将下标index往后移一位 62 | // 否则我们将n个字节后面的数据重新放回Data里. 63 | data = data[len(b):] 64 | if len(data) == 0 { 65 | iov.index++ 66 | } else { 67 | iov.Data[iov.index] = data 68 | } 69 | 70 | n -= len(b) 71 | iov.Length -= len(b) 72 | written += len(b) 73 | 74 | if _, err = w.Write(b); err != nil { 75 | return 76 | } 77 | } 78 | return 79 | } 80 | 81 | type IOVecWriter struct { 82 | fd uintptr 83 | smallBuffer []byte 84 | sysIOV []SysIOVec 85 | } 86 | 87 | func NewIOVecWriter(w io.Writer) (iow *IOVecWriter) { 88 | var err error 89 | var file *os.File 90 | 91 | // TODO:是否要增加其他的类型断言 92 | switch value := w.(type) { 93 | case *net.TCPConn: 94 | { 95 | file, err = value.File() 96 | if err != nil { 97 | return 98 | } 99 | } 100 | case *os.File: 101 | { 102 | file = value 103 | } 104 | default: 105 | return 106 | } 107 | 108 | iow = &IOVecWriter{ 109 | fd: file.Fd(), 110 | } 111 | 112 | return 113 | } 114 | 115 | // 1 2 3 4 5 6 116 | // --- -------------- --- --- --- ----------- 117 | // | | | | | | | | | | | | ...... 118 | // --- -------------- --- --- --- ----------- 119 | // 120 | // 1 -> 5个字节, 3 -> 15个字节, 4 -> 10个字节, 5 -> 15个字节 121 | 122 | // 1,3,4,5内存块太小(小于16个字节),因此我们将它组装起来为samllbuffer 123 | // 并且将Base置于每次组装smallBuffer前总长度的尾部. 124 | // 125 | // samllbuffer: 126 | // 1 3 4 5 ........ 127 | // ------------------------------ 128 | // | | 129 | // ------------------------------ 130 | // <--> 第一个小内存块,假设地址为0xF10000 131 | // 5 132 | // <------> 第二个小内存块,假设地址为0xF20000 133 | // 20 134 | // <----------> 第三个小内存块,假设地址为0xF30000 135 | // 30 136 | // <--------------> 第四个小内存块,假设地址为0xF40000 137 | // 45 138 | // 139 | // 开始Base == 每次组装smallBuffer尾部 140 | // 即: 141 | // Base1 = 0, smallBuffer += 5, 142 | // Base3 = 5, smallBuffer += 15, 143 | // Base4 = 20, smallBuffer += 10, 144 | // Base5 = 30, smallBuffer += 15, 145 | // 146 | // 然后我们将每一块内存块都取出来,比samllBuffer小的内存块,我们就将Base指向内存块的地址 147 | // 之前小于16个字节的内存块,肯定会比smallBuffer小,因为smallBuffer是所有小内存快的总和. 148 | // 即: 149 | // Base1 = &smallBuffer[0], Base1 = 0xF10000, 150 | // Base3 = &smallBuffer[5], Base3 = 0xF20000, 151 | // Base4 = &smallBuffer[20], Base4 = 0xF30000, 152 | // Base5 = &smallBuffer[30], Base5 = 0xF40000, 153 | 154 | func (iow *IOVecWriter) Write(data []byte) (written int, err error) { 155 | siov := SysIOVec{ 156 | Length: uint64(len(data)), 157 | } 158 | 159 | // unsafe.Pointer == void * 160 | // Base 用整数的形式来记录内存中有几个数据 161 | // 如果数据小于16,这个时候小块内存的Base还不是数据的内存地址 162 | if siov.Length < 16 { 163 | // Base 置于上一块samllBuffer的末尾 164 | // 然后拼接smallBuffer 165 | siov.Base = uintptr(len(iow.smallBuffer)) 166 | iow.smallBuffer = append(iow.smallBuffer, data...) 167 | } else { 168 | siov.Base = uintptr(unsafe.Pointer(&data[0])) 169 | } 170 | 171 | iow.sysIOV = append(iow.sysIOV, siov) 172 | 173 | return written, nil 174 | } 175 | 176 | func (iow *IOVecWriter) Flush() error { 177 | // 取出每一块内存 178 | for i, _ := range iow.sysIOV { 179 | siov := &iow.sysIOV[i] // 一定要拿地址,如果这里不是取地址,那么无法改变下面Base的值 180 | if siov.Base < uintptr(len(iow.smallBuffer)) { 181 | // 这个时候小块内存的Base就是数据的内存地址 182 | siov.Base = uintptr(unsafe.Pointer(&iow.smallBuffer[siov.Base])) 183 | } 184 | } 185 | 186 | N := 1024 187 | count := len(iow.sysIOV) 188 | // 每次最多取1024个内存块(不管是大内存块,还是小内存块) 189 | for i := 0; i < count; i += N { 190 | n := count - i 191 | if n > N { 192 | n = N 193 | } 194 | 195 | // _, _, errno := syscall.Syscall(syscall.SYS_WRITEV, iow.fd, uintptr(unsafe.Pointer(&iow.sysIOV[i])), uintptr(n)) 196 | // if errno != 0 { 197 | // return errors.New(errno.Error()) 198 | // } 199 | } 200 | 201 | iow.sysIOV = iow.sysIOV[:0] 202 | iow.smallBuffer = iow.smallBuffer[:0] 203 | 204 | return nil 205 | } 206 | -------------------------------------------------------------------------------- /ws/connection.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/micro-community/stream/ws/session" 8 | "go-micro.dev/v4/util/log" 9 | ) 10 | 11 | var upgrader = websocket.Upgrader{ 12 | CheckOrigin: func(r *http.Request) bool { return true }, 13 | } 14 | 15 | // verifyAuth token from head 16 | func verifyAuth(tokenStr string) (string, error) { 17 | // log.Info(token,check) 18 | // todo .. 19 | //send token server to verify auth . 20 | 21 | return "userID", nil 22 | } 23 | 24 | // HandleConn of websocket 25 | func HandleConn(w http.ResponseWriter, r *http.Request) { 26 | // Upgrade request to websocket 27 | conn, err := upgrader.Upgrade(w, r, nil) 28 | if err != nil { 29 | w.WriteHeader(http.StatusBadRequest) 30 | log.Fatal("Upgrade: ", err) 31 | return 32 | } 33 | defer conn.Close() 34 | 35 | var userID string 36 | ////to do 37 | // userID = verifyAuth() 38 | /// 39 | 40 | session.AddClient(userID, conn) 41 | 42 | session.RemoveClient(userID, conn) 43 | log.Infof("Stream complete") 44 | } 45 | -------------------------------------------------------------------------------- /ws/server.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "go-micro.dev/v4/util/log" 6 | ) 7 | 8 | // ServeStream Server Stream fo websocket 9 | func serveStream(ws *websocket.Conn) error { 10 | 11 | // Even if we aren't expecting further requests from the websocket, we still need to read from it to ensure we 12 | // get close signals 13 | go func() { 14 | for { 15 | if _, _, err := ws.NextReader(); err != nil { 16 | break 17 | } 18 | } 19 | }() 20 | log.Info("Received Request") 21 | // log.Infof("Received Request: %v", req) 22 | 23 | for { 24 | 25 | var rsp interface{} 26 | // Write server response to the websocket 27 | err := ws.WriteJSON(rsp) 28 | if err != nil { 29 | // End request if socket is closed 30 | if isExpectedClose(err) { 31 | log.Infof("Expected Close on socket", err) 32 | break 33 | } else { 34 | return err 35 | } 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func isExpectedClose(err error) bool { 43 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { 44 | log.Infof("Unexpected websocket close: ", err) 45 | return false 46 | } 47 | 48 | return true 49 | } 50 | -------------------------------------------------------------------------------- /ws/session/client.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "sync" 5 | 6 | "log" 7 | 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | // Client Connection 12 | type Client struct { 13 | userID string 14 | conns map[*websocket.Conn]*Connection 15 | } 16 | 17 | func (c *Client) add(conn *websocket.Conn) { 18 | 19 | _, ok := c.conns[conn] 20 | if ok { 21 | return 22 | } 23 | c.conns[conn] = &Connection{ 24 | conn: conn, 25 | } 26 | } 27 | 28 | func (c *Client) delete(conn *websocket.Conn) { 29 | delete(c.conns, conn) 30 | } 31 | 32 | func (c *Client) sendMessage(msg interface{}) { 33 | for _, conn := range c.conns { 34 | err := conn.WriteJSON(msg) 35 | if err != nil { 36 | log.Printf("conn.WriteJSON error:%v", err) 37 | } 38 | } 39 | } 40 | 41 | // parameters 42 | var ( 43 | clients = make(map[string]*Client) 44 | lock sync.RWMutex 45 | ) 46 | 47 | // SendMessage To User.. 48 | func SendMessage(msg interface{}, userIDs []string) { 49 | lock.RLock() 50 | for _, userID := range userIDs { 51 | client, exist := clients[userID] 52 | if exist { 53 | log.Printf("streaming log (data:%s), user(%s)", msg, userID) 54 | client.sendMessage(msg) 55 | } 56 | } 57 | lock.RUnlock() 58 | } 59 | -------------------------------------------------------------------------------- /ws/session/connection.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "sync" 6 | ) 7 | 8 | //Connection Connection 9 | type Connection struct { 10 | conn *websocket.Conn 11 | lock sync.Mutex 12 | } 13 | 14 | //WriteJSON ... 15 | func (c *Connection) WriteJSON(v interface{}) error { 16 | c.lock.Lock() 17 | defer c.lock.Unlock() 18 | return c.conn.WriteJSON(v) 19 | } 20 | -------------------------------------------------------------------------------- /ws/session/manager.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | ) 6 | 7 | //RemoveClient ... 8 | func RemoveClient(userID string, conn *websocket.Conn) { 9 | lock.Lock() 10 | client, ok := clients[userID] 11 | 12 | if !ok { 13 | lock.Unlock() 14 | return 15 | } 16 | 17 | client.delete(conn) 18 | if len(client.conns) == 0 { 19 | delete(clients, userID) 20 | } 21 | lock.Unlock() 22 | } 23 | 24 | //AddClient ... 25 | func AddClient(userID string, conn *websocket.Conn) { 26 | lock.Lock() 27 | client, ok := clients[userID] 28 | if !ok { 29 | client = &Client{ 30 | userID: userID, 31 | conns: make(map[*websocket.Conn]*Connection), 32 | } 33 | clients[userID] = client 34 | } 35 | client.add(conn) 36 | lock.Unlock() 37 | } 38 | --------------------------------------------------------------------------------