Repository: gwuhaolin/livego Branch: master Commit: 16c6af5d9031 Files: 79 Total size: 272.9 KB Directory structure: gitextract_je20vge0/ ├── .github/ │ └── workflows/ │ ├── codeql.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── README_cn.md ├── SECURITY.md ├── av/ │ ├── av.go │ └── rwbase.go ├── configure/ │ ├── channel.go │ └── liveconfig.go ├── container/ │ ├── flv/ │ │ ├── demuxer.go │ │ ├── muxer.go │ │ └── tag.go │ └── ts/ │ ├── crc32.go │ ├── muxer.go │ └── muxer_test.go ├── go.mod ├── go.sum ├── livego.yaml ├── main.go ├── parser/ │ ├── aac/ │ │ └── parser.go │ ├── h264/ │ │ ├── parser.go │ │ └── parser_test.go │ ├── mp3/ │ │ └── parser.go │ └── parser.go ├── protocol/ │ ├── amf/ │ │ ├── amf.go │ │ ├── amf_test.go │ │ ├── const.go │ │ ├── decoder_amf0.go │ │ ├── decoder_amf0_test.go │ │ ├── decoder_amf3.go │ │ ├── decoder_amf3_external.go │ │ ├── decoder_amf3_test.go │ │ ├── encoder_amf0.go │ │ ├── encoder_amf0_test.go │ │ ├── encoder_amf3.go │ │ ├── encoder_amf3_test.go │ │ ├── metadata.go │ │ └── util.go │ ├── api/ │ │ └── api.go │ ├── hls/ │ │ ├── align.go │ │ ├── audio_cache.go │ │ ├── cache.go │ │ ├── hls.go │ │ ├── item.go │ │ ├── source.go │ │ └── status.go │ ├── httpflv/ │ │ ├── server.go │ │ └── writer.go │ └── rtmp/ │ ├── cache/ │ │ ├── cache.go │ │ ├── gop.go │ │ └── special.go │ ├── core/ │ │ ├── chunk_stream.go │ │ ├── chunk_stream_test.go │ │ ├── conn.go │ │ ├── conn_client.go │ │ ├── conn_server.go │ │ ├── conn_test.go │ │ ├── handshake.go │ │ ├── read_writer.go │ │ └── read_writer_test.go │ ├── rtmp.go │ ├── rtmprelay/ │ │ ├── rtmprelay.go │ │ └── staticrelay.go │ └── stream.go ├── test.go └── utils/ ├── pio/ │ ├── pio.go │ ├── reader.go │ └── writer.go ├── pool/ │ └── pool.go ├── queue/ │ └── queue.go └── uid/ ├── rand.go └── uuid.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/codeql.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL Advanced" on: push: branches: [ "master" ] pull_request: branches: [ "master" ] schedule: - cron: '30 23 * * 3' jobs: analyze: name: Analyze (${{ matrix.language }}) # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read # only required for workflows in private repositories actions: read contents: read strategy: fail-fast: false matrix: include: - language: go build-mode: autobuild # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' # Use `c-cpp` to analyze code written in C, C++ or both # Use 'java-kotlin' to analyze code written in Java, Kotlin or both # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # If the analyze step fails for one of the languages you are analyzing with # "We were unable to automatically build your code", modify the matrix above # to set the build mode to "manual" for that language. Then modify this step # to build your code. # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - if: matrix.build-mode == 'manual' shell: bash run: | echo 'If you are using a "manual" build mode for one or more of the' \ 'languages you are analyzing, replace this with the commands to build' \ 'your code, for example:' echo ' make bootstrap' echo ' make release' exit 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: release: types: [published] jobs: goreleaser: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Set up Go uses: actions/setup-go@v5 with: go-version: '>=1.18.0' - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Get dependencies run: go get - name: Go release uses: goreleaser/goreleaser-action@v1 - name: Docker release uses: elgohr/Publish-Docker-Github-Action@master with: name: gwuhaolin/livego username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: [push] jobs: test: name: Build runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] steps: - name: Set up Go uses: actions/setup-go@v5 with: go-version: '>=1.18.0' - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Get dependencies run: go get - name: Test run: go test ./... ================================================ FILE: .gitignore ================================================ # Created by .ignore support plugin (hsz.mobi) .idea dist .vscode tmp vendor livego livego.exe ================================================ FILE: .goreleaser.yml ================================================ before: hooks: - go mod tidy builds: - binary: livego id: livego main: ./main.go goos: - windows - darwin - linux - freebsd goarch: - amd64 - 386 - arm ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added - JSON Web Token support. ``` json // livego.json { "jwt": { "secret": "testing", "algorithm": "HS256" }, "server": [ { "appname": "live", "live": true, "hls": true } ] } ``` - Use redis for store room keys ``` json // livego.json { "redis_addr": "localhost:6379", "server": [ { "appname": "live", "live": true, "hls": true } ] } ``` - Makefile ### Changed - Show `players`. - Show `stream_id`. - Deleted keys saved in physical file, now the keys are in cached using `go-cache` by default. - Using `logrus` like log system. - Using method `.Get(queryParamName)` to get an url query param. - Replaced `errors.New(...)` to `fmt.Errorf(...)`. - Replaced types string on config params `liveon` and `hlson` to booleans `live: true/false` and `hls: true/false` - Using viper for config, allow use file, cloud providers, environment vars or flags. - Using yaml config by default. ================================================ FILE: Dockerfile ================================================ FROM golang:latest as builder WORKDIR /app ENV GOPROXY https://goproxy.io COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego . FROM alpine:latest RUN mkdir -p /app/config WORKDIR /app ENV RTMP_PORT 1935 ENV HTTP_FLV_PORT 7001 ENV HLS_PORT 7002 ENV HTTP_OPERATION_PORT 8090 COPY --from=builder /app/livego . EXPOSE ${RTMP_PORT} EXPOSE ${HTTP_FLV_PORT} EXPOSE ${HLS_PORT} EXPOSE ${HTTP_OPERATION_PORT} ENTRYPOINT ["./livego"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 吴浩麟 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ GOCMD ?= go GOBUILD = $(GOCMD) build GOCLEAN = $(GOCMD) clean GOTEST = $(GOCMD) test GOGET = $(GOCMD) get BINARY_NAME = livego BINARY_UNIX = $(BINARY_NAME)_unix DOCKER_ACC ?= gwuhaolin DOCKER_REPO ?= livego TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null) default: all all: test build dockerize build: $(GOBUILD) -o $(BINARY_NAME) -v -ldflags="-X main.VERSION=$(TAG)" test: $(GOTEST) -v ./... clean: $(GOCLEAN) rm -f $(BINARY_NAME) rm -f $(BINARY_UNIX) run: build ./$(BINARY_NAME) build-linux: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v dockerize: docker build -t $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) . docker push $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) ================================================ FILE: README.md ================================================

[中文](./README_cn.md) [![Test](https://github.com/gwuhaolin/livego/actions/workflows/test.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/test.yml) [![Release](https://github.com/gwuhaolin/livego/actions/workflows/release.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/release.yml) [![CodeQL Advanced](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml) Simple and efficient live broadcast server: - Very simple to install and use; - Pure Golang, high performance, and cross-platform; - Supports commonly used transmission protocols, file formats, and encoding formats; #### Supported transport protocols - RTMP - AMF - HLS - HTTP-FLV #### Supported container formats - FLV - TS #### Supported encoding formats - H264 - AAC - MP3 ## Installation After directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line. #### Boot from Docker Run `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start #### Compile from source 1. Download the source code `git clone https://github.com/gwuhaolin/livego.git` 2. Go to the livego directory and execute `go build` or `make build` ## Use 1. Start the service: execute the livego binary file or `make run` to start the livego service; 2. Get a channelkey(used for push the video stream) from `http://localhost:8090/control/get?room=movie` and copy data like your channelkey. 3. Upstream push: Push the video stream to `rtmp://localhost:1935/{appname}/{channelkey}` through the` RTMP` protocol(default appname is `live`), for example, use `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` push([download demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv)); 4. Downstream playback: The following three playback protocols are supported, and the playback address is as follows: - `RTMP`:`rtmp://localhost:1935/{appname}/movie` - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv` - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8` 5. Use hls via https: generate ssl certificate(server.key, server.crt files), place them in directory with executable file, change "use_hls_https" option in livego.yaml to true (false by default) all options: ```bash ./livego -h Usage of ./livego: --api_addr string HTTP manage interface server listen address (default ":8090") --config_file string configure filename (default "livego.yaml") --flv_dir string output flv file at flvDir/APP/KEY_TIME.flv (default "tmp") --gop_num int gop num (default 1) --hls_addr string HLS server listen address (default ":7002") --hls_keep_after_end Maintains the HLS after the stream ends --httpflv_addr string HTTP-FLV server listen address (default ":7001") --level string Log level (default "info") --read_timeout int read time out (default 10) --rtmp_addr string RTMP server listen address ``` ### [Use with flv.js](https://github.com/gwuhaolin/blog/issues/3) Interested in Golang? Please see [Golang Chinese Learning Materials Summary](http://go.wuhaolin.cn/) ================================================ FILE: README_cn.md ================================================

[![Test](https://github.com/gwuhaolin/livego/workflows/Test/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest) [![Release](https://github.com/gwuhaolin/livego/workflows/Release/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease) 简单高效的直播服务器: - 安装和使用非常简单; - 纯 Golang 编写,性能高,跨平台; - 支持常用的传输协议、文件格式、编码格式; #### 支持的传输协议 - RTMP - AMF - HLS - HTTP-FLV #### 支持的容器格式 - FLV - TS #### 支持的编码格式 - H264 - AAC - MP3 ## 安装 直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后,在命令行中执行。 #### 从 Docker 启动 执行`docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego`启动 #### 从源码编译 1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git` 2. 去 livego 目录中 执行 `go build` ## 使用 1. 启动服务:执行 `livego` 二进制文件启动 livego 服务; 2. 访问 `http://localhost:8090/control/get?room=movie` 获取一个房间的 channelkey(channelkey用于推流,movie用于播放). 3. 推流: 通过`RTMP`协议推送视频流到地址 `rtmp://localhost:1935/{appname}/{channelkey}` (appname默认是`live`), 例如: 使用 `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` 推流([下载demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv)); 4. 播放: 支持多种播放协议,播放地址如下: - `RTMP`:`rtmp://localhost:1935/{appname}/movie` - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv` - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8` 所有配置项: ```bash ./livego -h Usage of ./livego: --api_addr string HTTP管理访问监听地址 (default ":8090") --config_file string 配置文件路径 (默认 "livego.yaml") --flv_dir string 输出的 flv 文件路径 flvDir/APP/KEY_TIME.flv (默认 "tmp") --gop_num int gop 数量 (default 1) --hls_addr string HLS 服务监听地址 (默认 ":7002") --hls_keep_after_end Maintains the HLS after the stream ends --httpflv_addr string HTTP-FLV server listen address (默认 ":7001") --level string 日志等级 (默认 "info") --read_timeout int 读超时时间 (默认 10) --rtmp_addr string RTMP 服务监听地址 (默认 ":1935") --write_timeout int 写超时时间 (默认 10) ``` ### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3) 对Golang感兴趣?请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/) ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions Use this section to tell people about which versions of your project are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | 5.1.x | :white_check_mark: | | 5.0.x | :x: | | 4.0.x | :white_check_mark: | | < 4.0 | :x: | ## Reporting a Vulnerability Use this section to tell people how to report a vulnerability. Tell them where to go, how often they can expect to get an update on a reported vulnerability, what to expect if the vulnerability is accepted or declined, etc. ================================================ FILE: av/av.go ================================================ package av import ( "fmt" "io" ) const ( TAG_AUDIO = 8 TAG_VIDEO = 9 TAG_SCRIPTDATAAMF0 = 18 TAG_SCRIPTDATAAMF3 = 0xf ) const ( MetadatAMF0 = 0x12 MetadataAMF3 = 0xf ) const ( SOUND_MP3 = 2 SOUND_NELLYMOSER_16KHZ_MONO = 4 SOUND_NELLYMOSER_8KHZ_MONO = 5 SOUND_NELLYMOSER = 6 SOUND_ALAW = 7 SOUND_MULAW = 8 SOUND_AAC = 10 SOUND_SPEEX = 11 SOUND_5_5Khz = 0 SOUND_11Khz = 1 SOUND_22Khz = 2 SOUND_44Khz = 3 SOUND_8BIT = 0 SOUND_16BIT = 1 SOUND_MONO = 0 SOUND_STEREO = 1 AAC_SEQHDR = 0 AAC_RAW = 1 ) const ( AVC_SEQHDR = 0 AVC_NALU = 1 AVC_EOS = 2 FRAME_KEY = 1 FRAME_INTER = 2 VIDEO_H264 = 7 ) var ( PUBLISH = "publish" PLAY = "play" ) // Header can be converted to AudioHeaderInfo or VideoHeaderInfo type Packet struct { IsAudio bool IsVideo bool IsMetadata bool TimeStamp uint32 // dts StreamID uint32 Header PacketHeader Data []byte } type PacketHeader interface { } type AudioPacketHeader interface { PacketHeader SoundFormat() uint8 AACPacketType() uint8 } type VideoPacketHeader interface { PacketHeader IsKeyFrame() bool IsSeq() bool CodecID() uint8 CompositionTime() int32 } type Demuxer interface { Demux(*Packet) (ret *Packet, err error) } type Muxer interface { Mux(*Packet, io.Writer) error } type SampleRater interface { SampleRate() (int, error) } type CodecParser interface { SampleRater Parse(*Packet, io.Writer) error } type GetWriter interface { GetWriter(Info) WriteCloser } type Handler interface { HandleReader(ReadCloser) HandleWriter(WriteCloser) } type Alive interface { Alive() bool } type Closer interface { Info() Info Close(error) } type CalcTime interface { CalcBaseTimestamp() } type Info struct { Key string URL string UID string Inter bool } func (info Info) IsInterval() bool { return info.Inter } func (info Info) String() string { return fmt.Sprintf("", info.Key, info.URL, info.UID, info.Inter) } type ReadCloser interface { Closer Alive Read(*Packet) error } type WriteCloser interface { Closer Alive CalcTime Write(*Packet) error } ================================================ FILE: av/rwbase.go ================================================ package av import ( "sync" "time" ) type RWBaser struct { lock sync.Mutex timeout time.Duration PreTime time.Time BaseTimestamp uint32 LastVideoTimestamp uint32 LastAudioTimestamp uint32 } func NewRWBaser(duration time.Duration) RWBaser { return RWBaser{ timeout: duration, PreTime: time.Now(), } } func (rw *RWBaser) BaseTimeStamp() uint32 { return rw.BaseTimestamp } func (rw *RWBaser) CalcBaseTimestamp() { if rw.LastAudioTimestamp > rw.LastVideoTimestamp { rw.BaseTimestamp = rw.LastAudioTimestamp } else { rw.BaseTimestamp = rw.LastVideoTimestamp } } func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) { if typeID == TAG_VIDEO { rw.LastVideoTimestamp = timestamp } else if typeID == TAG_AUDIO { rw.LastAudioTimestamp = timestamp } } func (rw *RWBaser) SetPreTime() { rw.lock.Lock() rw.PreTime = time.Now() rw.lock.Unlock() } func (rw *RWBaser) Alive() bool { rw.lock.Lock() b := !(time.Now().Sub(rw.PreTime) >= rw.timeout) rw.lock.Unlock() return b } ================================================ FILE: configure/channel.go ================================================ package configure import ( "fmt" "github.com/gwuhaolin/livego/utils/uid" "github.com/go-redis/redis/v7" "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) type RoomKeysType struct { redisCli *redis.Client localCache *cache.Cache } var RoomKeys = &RoomKeysType{ localCache: cache.New(cache.NoExpiration, 0), } var saveInLocal = true func Init() { saveInLocal = len(Config.GetString("redis_addr")) == 0 if saveInLocal { return } RoomKeys.redisCli = redis.NewClient(&redis.Options{ Addr: Config.GetString("redis_addr"), Password: Config.GetString("redis_pwd"), DB: 0, }) _, err := RoomKeys.redisCli.Ping().Result() if err != nil { log.Panic("Redis: ", err) } log.Info("Redis connected") } // set/reset a random key for channel func (r *RoomKeysType) SetKey(channel string) (key string, err error) { if !saveInLocal { for { key = uid.RandStringRunes(48) if _, err = r.redisCli.Get(key).Result(); err == redis.Nil { err = r.redisCli.Set(channel, key, 0).Err() if err != nil { return } err = r.redisCli.Set(key, channel, 0).Err() return } else if err != nil { return } } } for { key = uid.RandStringRunes(48) if _, found := r.localCache.Get(key); !found { r.localCache.SetDefault(channel, key) r.localCache.SetDefault(key, channel) break } } return } func (r *RoomKeysType) GetKey(channel string) (newKey string, err error) { if !saveInLocal { if newKey, err = r.redisCli.Get(channel).Result(); err == redis.Nil { newKey, err = r.SetKey(channel) log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) return } return } var key interface{} var found bool if key, found = r.localCache.Get(channel); found { return key.(string), nil } newKey, err = r.SetKey(channel) log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) return } func (r *RoomKeysType) GetChannel(key string) (channel string, err error) { if !saveInLocal { return r.redisCli.Get(key).Result() } chann, found := r.localCache.Get(key) if found { return chann.(string), nil } else { return "", fmt.Errorf("%s does not exists", key) } } func (r *RoomKeysType) DeleteChannel(channel string) bool { if !saveInLocal { return r.redisCli.Del(channel).Err() != nil } key, ok := r.localCache.Get(channel) if ok { r.localCache.Delete(channel) r.localCache.Delete(key.(string)) return true } return false } func (r *RoomKeysType) DeleteKey(key string) bool { if !saveInLocal { return r.redisCli.Del(key).Err() != nil } channel, ok := r.localCache.Get(key) if ok { r.localCache.Delete(channel.(string)) r.localCache.Delete(key) return true } return false } ================================================ FILE: configure/liveconfig.go ================================================ package configure import ( "bytes" "encoding/json" "strings" "github.com/kr/pretty" log "github.com/sirupsen/logrus" "github.com/spf13/pflag" "github.com/spf13/viper" ) /* { "server": [ { "appname": "live", "live": true, "hls": true, "static_push": [] } ] } */ type Application struct { Appname string `mapstructure:"appname"` Live bool `mapstructure:"live"` Hls bool `mapstructure:"hls"` Flv bool `mapstructure:"flv"` Api bool `mapstructure:"api"` StaticPush []string `mapstructure:"static_push"` } type Applications []Application type JWT struct { Secret string `mapstructure:"secret"` Algorithm string `mapstructure:"algorithm"` } type ServerCfg struct { Level string `mapstructure:"level"` ConfigFile string `mapstructure:"config_file"` FLVArchive bool `mapstructure:"flv_archive"` FLVDir string `mapstructure:"flv_dir"` RTMPNoAuth bool `mapstructure:"rtmp_noauth"` RTMPAddr string `mapstructure:"rtmp_addr"` HTTPFLVAddr string `mapstructure:"httpflv_addr"` HLSAddr string `mapstructure:"hls_addr"` HLSKeepAfterEnd bool `mapstructure:"hls_keep_after_end"` APIAddr string `mapstructure:"api_addr"` RedisAddr string `mapstructure:"redis_addr"` RedisPwd string `mapstructure:"redis_pwd"` ReadTimeout int `mapstructure:"read_timeout"` WriteTimeout int `mapstructure:"write_timeout"` EnableTLSVerify bool `mapstructure:"enable_tls_verify"` GopNum int `mapstructure:"gop_num"` JWT JWT `mapstructure:"jwt"` Server Applications `mapstructure:"server"` } // default config var defaultConf = ServerCfg{ ConfigFile: "livego.yaml", FLVArchive: false, RTMPNoAuth: false, RTMPAddr: ":1935", HTTPFLVAddr: ":7001", HLSAddr: ":7002", HLSKeepAfterEnd: false, APIAddr: ":8090", WriteTimeout: 10, ReadTimeout: 10, EnableTLSVerify: true, GopNum: 1, Server: Applications{{ Appname: "live", Live: true, Hls: true, Flv: true, Api: true, StaticPush: nil, }}, } var ( Config = viper.New() // BypassInit can be used to bypass the init() function by setting this // value to True at compile time. // // go build -ldflags "-X 'github.com/gwuhaolin/livego/configure.BypassInit=true'" -o livego main.go BypassInit string = "" ) func initLog() { if l, err := log.ParseLevel(Config.GetString("level")); err == nil { log.SetLevel(l) log.SetReportCaller(l == log.DebugLevel) } } func init() { if BypassInit == "" { initDefault() } } func initDefault() { defer Init() // Default config b, _ := json.Marshal(defaultConf) defaultConfig := bytes.NewReader(b) viper.SetConfigType("json") viper.ReadConfig(defaultConfig) Config.MergeConfigMap(viper.AllSettings()) // Flags pflag.String("rtmp_addr", ":1935", "RTMP server listen address") pflag.Bool("enable_rtmps", false, "enable server session RTMPS") pflag.String("rtmps_cert", "server.crt", "cert file path required for RTMPS") pflag.String("rtmps_key", "server.key", "key file path required for RTMPS") pflag.String("httpflv_addr", ":7001", "HTTP-FLV server listen address") pflag.String("hls_addr", ":7002", "HLS server listen address") pflag.String("api_addr", ":8090", "HTTP manage interface server listen address") pflag.String("config_file", "livego.yaml", "configure filename") pflag.String("level", "info", "Log level") pflag.Bool("hls_keep_after_end", false, "Maintains the HLS after the stream ends") pflag.String("flv_dir", "tmp", "output flv file at flvDir/APP/KEY_TIME.flv") pflag.Int("read_timeout", 10, "read time out") pflag.Int("write_timeout", 10, "write time out") pflag.Int("gop_num", 1, "gop num") pflag.Bool("enable_tls_verify", true, "Use system root CA to verify RTMPS connection, set this flag to false on Windows") pflag.Parse() Config.BindPFlags(pflag.CommandLine) // File Config.SetConfigFile(Config.GetString("config_file")) Config.AddConfigPath(".") err := Config.ReadInConfig() if err != nil { log.Warning(err) log.Info("Using default config") } else { Config.MergeInConfig() } // Environment replacer := strings.NewReplacer(".", "_") Config.SetEnvKeyReplacer(replacer) Config.AllowEmptyEnv(true) Config.AutomaticEnv() // Log initLog() // Print final config c := ServerCfg{} Config.Unmarshal(&c) log.Debugf("Current configurations: \n%# v", pretty.Formatter(c)) } func CheckAppName(appname string) bool { apps := Applications{} Config.UnmarshalKey("server", &apps) for _, app := range apps { if app.Appname == appname { return app.Live } } return false } func GetStaticPushUrlList(appname string) ([]string, bool) { apps := Applications{} Config.UnmarshalKey("server", &apps) for _, app := range apps { if (app.Appname == appname) && app.Live { if len(app.StaticPush) > 0 { return app.StaticPush, true } else { return nil, false } } } return nil, false } ================================================ FILE: container/flv/demuxer.go ================================================ package flv import ( "fmt" "github.com/gwuhaolin/livego/av" ) var ( ErrAvcEndSEQ = fmt.Errorf("avc end sequence") ) type Demuxer struct { } func NewDemuxer() *Demuxer { return &Demuxer{} } func (d *Demuxer) DemuxH(p *av.Packet) error { var tag Tag _, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo) if err != nil { return err } p.Header = &tag return nil } func (d *Demuxer) Demux(p *av.Packet) error { var tag Tag n, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo) if err != nil { return err } if tag.CodecID() == av.VIDEO_H264 && p.Data[0] == 0x17 && p.Data[1] == 0x02 { return ErrAvcEndSEQ } p.Header = &tag p.Data = p.Data[n:] return nil } ================================================ FILE: container/flv/muxer.go ================================================ package flv import ( "fmt" "os" "path" "strings" "time" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/protocol/amf" "github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/uid" log "github.com/sirupsen/logrus" ) var ( flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09} ) /* func NewFlv(handler av.Handler, info av.Info) { patths := strings.SplitN(info.Key, "/", 2) if len(patths) != 2 { log.Warning("invalid info") return } w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755) if err != nil { log.Error("open file error: ", err) } writer := NewFLVWriter(patths[0], patths[1], info.URL, w) handler.HandleWriter(writer) writer.Wait() // close flv file log.Debug("close flv file") writer.ctx.Close() } */ const ( headerLen = 11 ) type FLVWriter struct { Uid string av.RWBaser app, title, url string buf []byte closed chan struct{} ctx *os.File closedWriter bool } func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter { ret := &FLVWriter{ Uid: uid.NewId(), app: app, title: title, url: url, ctx: ctx, RWBaser: av.NewRWBaser(time.Second * 10), closed: make(chan struct{}), buf: make([]byte, headerLen), } ret.ctx.Write(flvHeader) pio.PutI32BE(ret.buf[:4], 0) ret.ctx.Write(ret.buf[:4]) return ret } func (writer *FLVWriter) Write(p *av.Packet) error { writer.RWBaser.SetPreTime() h := writer.buf[:headerLen] typeID := av.TAG_VIDEO if !p.IsVideo { if p.IsMetadata { var err error typeID = av.TAG_SCRIPTDATAAMF0 p.Data, err = amf.MetaDataReform(p.Data, amf.DEL) if err != nil { return err } } else { typeID = av.TAG_AUDIO } } dataLen := len(p.Data) timestamp := p.TimeStamp timestamp += writer.BaseTimeStamp() writer.RWBaser.RecTimeStamp(timestamp, uint32(typeID)) preDataLen := dataLen + headerLen timestampbase := timestamp & 0xffffff timestampExt := timestamp >> 24 & 0xff pio.PutU8(h[0:1], uint8(typeID)) pio.PutI24BE(h[1:4], int32(dataLen)) pio.PutI24BE(h[4:7], int32(timestampbase)) pio.PutU8(h[7:8], uint8(timestampExt)) if _, err := writer.ctx.Write(h); err != nil { return err } if _, err := writer.ctx.Write(p.Data); err != nil { return err } pio.PutI32BE(h[:4], int32(preDataLen)) if _, err := writer.ctx.Write(h[:4]); err != nil { return err } return nil } func (writer *FLVWriter) Wait() { select { case <-writer.closed: return } } func (writer *FLVWriter) Close(error) { if writer.closedWriter { return } writer.closedWriter = true writer.ctx.Close() close(writer.closed) } func (writer *FLVWriter) Info() (ret av.Info) { ret.UID = writer.Uid ret.URL = writer.url ret.Key = writer.app + "/" + writer.title return } type FlvDvr struct{} func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser { paths := strings.SplitN(info.Key, "/", 2) if len(paths) != 2 { log.Warning("invalid info") return nil } flvDir := configure.Config.GetString("flv_dir") err := os.MkdirAll(path.Join(flvDir, paths[0]), 0755) if err != nil { log.Error("mkdir error: ", err) return nil } fileName := fmt.Sprintf("%s_%d.%s", path.Join(flvDir, info.Key), time.Now().Unix(), "flv") log.Debug("flv dvr save stream to: ", fileName) w, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0755) if err != nil { log.Error("open file error: ", err) return nil } writer := NewFLVWriter(paths[0], paths[1], info.URL, w) log.Debug("new flv dvr: ", writer.Info()) return writer } ================================================ FILE: container/flv/tag.go ================================================ package flv import ( "fmt" "github.com/gwuhaolin/livego/av" ) type flvTag struct { fType uint8 dataSize uint32 timeStamp uint32 streamID uint32 // always 0 } type mediaTag struct { /* SoundFormat: UB[4] 0 = Linear PCM, platform endian 1 = ADPCM 2 = MP3 3 = Linear PCM, little endian 4 = Nellymoser 16-kHz mono 5 = Nellymoser 8-kHz mono 6 = Nellymoser 7 = G.711 A-law logarithmic PCM 8 = G.711 mu-law logarithmic PCM 9 = reserved 10 = AAC 11 = Speex 14 = MP3 8-Khz 15 = Device-specific sound Formats 7, 8, 14, and 15 are reserved for internal use AAC is supported in Flash Player 9,0,115,0 and higher. Speex is supported in Flash Player 10 and higher. */ soundFormat uint8 /* SoundRate: UB[2] Sampling rate 0 = 5.5-kHz For AAC: always 3 1 = 11-kHz 2 = 22-kHz 3 = 44-kHz */ soundRate uint8 /* SoundSize: UB[1] 0 = snd8Bit 1 = snd16Bit Size of each sample. This parameter only pertains to uncompressed formats. Compressed formats always decode to 16 bits internally */ soundSize uint8 /* SoundType: UB[1] 0 = sndMono 1 = sndStereo Mono or stereo sound For Nellymoser: always 0 For AAC: always 1 */ soundType uint8 /* 0: AAC sequence header 1: AAC raw */ aacPacketType uint8 /* 1: keyframe (for AVC, a seekable frame) 2: inter frame (for AVC, a non- seekable frame) 3: disposable inter frame (H.263 only) 4: generated keyframe (reserved for server use only) 5: video info/command frame */ frameType uint8 /* 1: JPEG (currently unused) 2: Sorenson H.263 3: Screen video 4: On2 VP6 5: On2 VP6 with alpha channel 6: Screen video version 2 7: AVC */ codecID uint8 /* 0: AVC sequence header 1: AVC NALU 2: AVC end of sequence (lower level NALU sequence ender is not required or supported) */ avcPacketType uint8 compositionTime int32 } type Tag struct { flvt flvTag mediat mediaTag } func (tag *Tag) SoundFormat() uint8 { return tag.mediat.soundFormat } func (tag *Tag) AACPacketType() uint8 { return tag.mediat.aacPacketType } func (tag *Tag) IsKeyFrame() bool { return tag.mediat.frameType == av.FRAME_KEY } func (tag *Tag) IsSeq() bool { return tag.mediat.frameType == av.FRAME_KEY && tag.mediat.avcPacketType == av.AVC_SEQHDR } func (tag *Tag) CodecID() uint8 { return tag.mediat.codecID } func (tag *Tag) CompositionTime() int32 { return tag.mediat.compositionTime } // ParseMediaTagHeader, parse video, audio, tag header func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) { switch isVideo { case false: n, err = tag.parseAudioHeader(b) case true: n, err = tag.parseVideoHeader(b) } return } func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) { if len(b) < n+1 { err = fmt.Errorf("invalid audiodata len=%d", len(b)) return } flags := b[0] tag.mediat.soundFormat = flags >> 4 tag.mediat.soundRate = (flags >> 2) & 0x3 tag.mediat.soundSize = (flags >> 1) & 0x1 tag.mediat.soundType = flags & 0x1 n++ switch tag.mediat.soundFormat { case av.SOUND_AAC: tag.mediat.aacPacketType = b[1] n++ } return } func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) { if len(b) < n+5 { err = fmt.Errorf("invalid videodata len=%d", len(b)) return } flags := b[0] tag.mediat.frameType = flags >> 4 tag.mediat.codecID = flags & 0xf n++ if tag.mediat.frameType == av.FRAME_INTER || tag.mediat.frameType == av.FRAME_KEY { tag.mediat.avcPacketType = b[1] for i := 2; i < 5; i++ { tag.mediat.compositionTime = tag.mediat.compositionTime<<8 + int32(b[i]) } n += 4 } return } ================================================ FILE: container/ts/crc32.go ================================================ package ts func GenCrc32(src []byte) uint32 { crcTable := []uint32{ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4} j := byte(0) crc32 := uint32(0xFFFFFFFF) for i := 0; i < len(src); i++ { j = (byte(crc32>>24) ^ src[i]) & 0xff crc32 = uint32(uint32(crc32<<8) ^ uint32(crcTable[j])) } return crc32 } ================================================ FILE: container/ts/muxer.go ================================================ package ts import ( "io" "github.com/gwuhaolin/livego/av" ) const ( tsDefaultDataLen = 184 tsPacketLen = 188 h264DefaultHZ = 90 videoPID = 0x100 audioPID = 0x101 videoSID = 0xe0 audioSID = 0xc0 ) type Muxer struct { videoCc byte audioCc byte patCc byte pmtCc byte pat [tsPacketLen]byte pmt [tsPacketLen]byte tsPacket [tsPacketLen]byte } func NewMuxer() *Muxer { return &Muxer{} } func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error { first := true wBytes := 0 pesIndex := 0 tmpLen := byte(0) dataLen := byte(0) var pes pesHeader dts := int64(p.TimeStamp) * int64(h264DefaultHZ) pts := dts pid := audioPID var videoH av.VideoPacketHeader if p.IsVideo { pid = videoPID videoH, _ = p.Header.(av.VideoPacketHeader) pts = dts + int64(videoH.CompositionTime())*int64(h264DefaultHZ) } err := pes.packet(p, pts, dts) if err != nil { return err } pesHeaderLen := pes.len packetBytesLen := len(p.Data) + int(pesHeaderLen) for { if packetBytesLen <= 0 { break } if p.IsVideo { muxer.videoCc++ if muxer.videoCc > 0xf { muxer.videoCc = 0 } } else { muxer.audioCc++ if muxer.audioCc > 0xf { muxer.audioCc = 0 } } i := byte(0) //sync byte muxer.tsPacket[i] = 0x47 i++ //error indicator, unit start indicator,ts priority,pid muxer.tsPacket[i] = byte(pid >> 8) //pid high 5 bits if first { muxer.tsPacket[i] = muxer.tsPacket[i] | 0x40 //unit start indicator } i++ //pid low 8 bits muxer.tsPacket[i] = byte(pid) i++ //scram control, adaptation control, counter if p.IsVideo { muxer.tsPacket[i] = 0x10 | byte(muxer.videoCc&0x0f) } else { muxer.tsPacket[i] = 0x10 | byte(muxer.audioCc&0x0f) } i++ //关键帧需要加pcr if first && p.IsVideo && videoH.IsKeyFrame() { muxer.tsPacket[3] |= 0x20 muxer.tsPacket[i] = 7 i++ muxer.tsPacket[i] = 0x50 i++ muxer.writePcr(muxer.tsPacket[0:], i, dts) i += 6 } //frame data if packetBytesLen >= tsDefaultDataLen { dataLen = tsDefaultDataLen if first { dataLen -= (i - 4) } } else { muxer.tsPacket[3] |= 0x20 //have adaptation remainBytes := byte(0) dataLen = byte(packetBytesLen) if first { remainBytes = tsDefaultDataLen - dataLen - (i - 4) } else { remainBytes = tsDefaultDataLen - dataLen } muxer.adaptationBufInit(muxer.tsPacket[i:], byte(remainBytes)) i += remainBytes } if first && i < tsPacketLen && pesHeaderLen > 0 { tmpLen = tsPacketLen - i if pesHeaderLen <= tmpLen { tmpLen = pesHeaderLen } copy(muxer.tsPacket[i:], pes.data[pesIndex:pesIndex+int(tmpLen)]) i += tmpLen packetBytesLen -= int(tmpLen) dataLen -= tmpLen pesHeaderLen -= tmpLen pesIndex += int(tmpLen) } if i < tsPacketLen { tmpLen = tsPacketLen - i if tmpLen <= dataLen { dataLen = tmpLen } copy(muxer.tsPacket[i:], p.Data[wBytes:wBytes+int(dataLen)]) wBytes += int(dataLen) packetBytesLen -= int(dataLen) } if w != nil { if _, err := w.Write(muxer.tsPacket[0:]); err != nil { return err } } first = false } return nil } //PAT return pat data func (muxer *Muxer) PAT() []byte { i := 0 remainByte := 0 tsHeader := []byte{0x47, 0x40, 0x00, 0x10, 0x00} patHeader := []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x01} if muxer.patCc > 0xf { muxer.patCc = 0 } tsHeader[3] |= muxer.patCc & 0x0f muxer.patCc++ copy(muxer.pat[i:], tsHeader) i += len(tsHeader) copy(muxer.pat[i:], patHeader) i += len(patHeader) crc32Value := GenCrc32(patHeader) muxer.pat[i] = byte(crc32Value >> 24) i++ muxer.pat[i] = byte(crc32Value >> 16) i++ muxer.pat[i] = byte(crc32Value >> 8) i++ muxer.pat[i] = byte(crc32Value) i++ remainByte = int(tsPacketLen - i) for j := 0; j < remainByte; j++ { muxer.pat[i+j] = 0xff } return muxer.pat[0:] } // PMT return pmt data func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte { i := int(0) j := int(0) var progInfo []byte remainBytes := int(0) tsHeader := []byte{0x47, 0x50, 0x01, 0x10, 0x00} pmtHeader := []byte{0x02, 0xb0, 0xff, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00} if !hasVideo { pmtHeader[9] = 0x01 progInfo = []byte{0x0f, 0xe1, 0x01, 0xf0, 0x00} } else { progInfo = []byte{0x1b, 0xe1, 0x00, 0xf0, 0x00, //h264 or h265* 0x0f, 0xe1, 0x01, 0xf0, 0x00, //mp3 or aac } } pmtHeader[2] = byte(len(progInfo) + 9 + 4) if muxer.pmtCc > 0xf { muxer.pmtCc = 0 } tsHeader[3] |= muxer.pmtCc & 0x0f muxer.pmtCc++ if soundFormat == 2 || soundFormat == 14 { if hasVideo { progInfo[5] = 0x4 } else { progInfo[0] = 0x4 } } copy(muxer.pmt[i:], tsHeader) i += len(tsHeader) copy(muxer.pmt[i:], pmtHeader) i += len(pmtHeader) copy(muxer.pmt[i:], progInfo[0:]) i += len(progInfo) crc32Value := GenCrc32(muxer.pmt[5 : 5+len(pmtHeader)+len(progInfo)]) muxer.pmt[i] = byte(crc32Value >> 24) i++ muxer.pmt[i] = byte(crc32Value >> 16) i++ muxer.pmt[i] = byte(crc32Value >> 8) i++ muxer.pmt[i] = byte(crc32Value) i++ remainBytes = int(tsPacketLen - i) for j = 0; j < remainBytes; j++ { muxer.pmt[i+j] = 0xff } return muxer.pmt[0:] } func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) { src[0] = byte(remainBytes - 1) if remainBytes == 1 { } else { src[1] = 0x00 for i := 2; i < len(src); i++ { src[i] = 0xff } } return } func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error { b[i] = byte(pcr >> 25) i++ b[i] = byte((pcr >> 17) & 0xff) i++ b[i] = byte((pcr >> 9) & 0xff) i++ b[i] = byte((pcr >> 1) & 0xff) i++ b[i] = byte(((pcr & 0x1) << 7) | 0x7e) i++ b[i] = 0x00 return nil } type pesHeader struct { len byte data [tsPacketLen]byte } // packet return pes packet func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error { //PES header i := 0 header.data[i] = 0x00 i++ header.data[i] = 0x00 i++ header.data[i] = 0x01 i++ sid := audioSID if p.IsVideo { sid = videoSID } header.data[i] = byte(sid) i++ flag := 0x80 ptslen := 5 dtslen := ptslen headerSize := ptslen if p.IsVideo && pts != dts { flag |= 0x40 headerSize += 5 //add dts } size := len(p.Data) + headerSize + 3 if size > 0xffff { size = 0 } header.data[i] = byte(size >> 8) i++ header.data[i] = byte(size) i++ header.data[i] = 0x80 i++ header.data[i] = byte(flag) i++ header.data[i] = byte(headerSize) i++ header.writeTs(header.data[0:], i, flag>>6, pts) i += ptslen if p.IsVideo && pts != dts { header.writeTs(header.data[0:], i, 1, dts) i += dtslen } header.len = byte(i) return nil } func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) { val := uint32(0) if ts > 0x1ffffffff { ts -= 0x1ffffffff } val = uint32(fb<<4) | ((uint32(ts>>30) & 0x07) << 1) | 1 src[i] = byte(val) i++ val = ((uint32(ts>>15) & 0x7fff) << 1) | 1 src[i] = byte(val >> 8) i++ src[i] = byte(val) i++ val = (uint32(ts&0x7fff) << 1) | 1 src[i] = byte(val >> 8) i++ src[i] = byte(val) } ================================================ FILE: container/ts/muxer_test.go ================================================ package ts import ( "testing" "github.com/gwuhaolin/livego/av" "github.com/stretchr/testify/assert" ) type TestWriter struct { buf []byte count int } //Write write p to w.buf func (w *TestWriter) Write(p []byte) (int, error) { w.count++ w.buf = p return len(p), nil } func TestTSEncoder(t *testing.T) { at := assert.New(t) m := NewMuxer() w := &TestWriter{} data := []byte{0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38, } p := av.Packet{ IsVideo: false, Data: data, } err := m.Mux(&p, w) at.Equal(err, nil) at.Equal(w.count, 1) at.Equal(w.buf, []byte{0x47, 0x41, 0x01, 0x31, 0x81, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x30, 0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01, 0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38}) } ================================================ FILE: go.mod ================================================ module github.com/gwuhaolin/livego go 1.13 require ( github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-redis/redis/v7 v7.2.0 github.com/gorilla/mux v1.7.4 // indirect github.com/kr/pretty v0.1.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.5.0 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.6.3 github.com/stretchr/testify v1.4.0 github.com/urfave/negroni v1.0.0 // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw= github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: livego.yaml ================================================ # # Logger level # level: info # # FLV Options # flv_archive: false # flv_dir: "./tmp" # httpflv_addr: ":7001" # # RTMP Options # rtmp_noauth: false # rtmp_addr: ":1935" # enable_rtmps: true # rtmps_cert: server.crt # rtmps_key: server.key # read_timeout: 10 # write_timeout: 10 # # HLS Options # hls_addr: ":7002" #use_hls_https: true # # API Options # api_addr: ":8090" server: - appname: live live: true hls: true api: true flv: true ================================================ FILE: main.go ================================================ package main import ( "crypto/tls" "fmt" "net" "path" "runtime" "time" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/protocol/api" "github.com/gwuhaolin/livego/protocol/hls" "github.com/gwuhaolin/livego/protocol/httpflv" "github.com/gwuhaolin/livego/protocol/rtmp" log "github.com/sirupsen/logrus" ) var VERSION = "master" func startHls() *hls.Server { hlsAddr := configure.Config.GetString("hls_addr") hlsListen, err := net.Listen("tcp", hlsAddr) if err != nil { log.Fatal(err) } hlsServer := hls.NewServer() go func() { defer func() { if r := recover(); r != nil { log.Error("HLS server panic: ", r) } }() log.Info("HLS listen On ", hlsAddr) hlsServer.Serve(hlsListen) }() return hlsServer } func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) { rtmpAddr := configure.Config.GetString("rtmp_addr") isRtmps := configure.Config.GetBool("enable_rtmps") var rtmpListen net.Listener if isRtmps { certPath := configure.Config.GetString("rtmps_cert") keyPath := configure.Config.GetString("rtmps_key") cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { log.Fatal(err) } rtmpListen, err = tls.Listen("tcp", rtmpAddr, &tls.Config{ Certificates: []tls.Certificate{cert}, }) if err != nil { log.Fatal(err) } } else { var err error rtmpListen, err = net.Listen("tcp", rtmpAddr) if err != nil { log.Fatal(err) } } var rtmpServer *rtmp.Server if hlsServer == nil { rtmpServer = rtmp.NewRtmpServer(stream, nil) log.Info("HLS server disable....") } else { rtmpServer = rtmp.NewRtmpServer(stream, hlsServer) log.Info("HLS server enable....") } defer func() { if r := recover(); r != nil { log.Error("RTMP server panic: ", r) } }() if isRtmps { log.Info("RTMPS Listen On ", rtmpAddr) } else { log.Info("RTMP Listen On ", rtmpAddr) } rtmpServer.Serve(rtmpListen) } func startHTTPFlv(stream *rtmp.RtmpStream) { httpflvAddr := configure.Config.GetString("httpflv_addr") flvListen, err := net.Listen("tcp", httpflvAddr) if err != nil { log.Fatal(err) } hdlServer := httpflv.NewServer(stream) go func() { defer func() { if r := recover(); r != nil { log.Error("HTTP-FLV server panic: ", r) } }() log.Info("HTTP-FLV listen On ", httpflvAddr) hdlServer.Serve(flvListen) }() } func startAPI(stream *rtmp.RtmpStream) { apiAddr := configure.Config.GetString("api_addr") rtmpAddr := configure.Config.GetString("rtmp_addr") if apiAddr != "" { opListen, err := net.Listen("tcp", apiAddr) if err != nil { log.Fatal(err) } opServer := api.NewServer(stream, rtmpAddr) go func() { defer func() { if r := recover(); r != nil { log.Error("HTTP-API server panic: ", r) } }() log.Info("HTTP-API listen On ", apiAddr) opServer.Serve(opListen) }() } } func init() { log.SetFormatter(&log.TextFormatter{ FullTimestamp: true, CallerPrettyfier: func(f *runtime.Frame) (string, string) { filename := path.Base(f.File) return fmt.Sprintf("%s()", f.Function), fmt.Sprintf(" %s:%d", filename, f.Line) }, }) } func main() { defer func() { if r := recover(); r != nil { log.Error("livego panic: ", r) time.Sleep(1 * time.Second) } }() log.Infof(` _ _ ____ | | (_)_ _____ / ___| ___ | | | \ \ / / _ \ | _ / _ \ | |___| |\ V / __/ |_| | (_) | |_____|_| \_/ \___|\____|\___/ version: %s `, VERSION) apps := configure.Applications{} configure.Config.UnmarshalKey("server", &apps) for _, app := range apps { stream := rtmp.NewRtmpStream() var hlsServer *hls.Server if app.Hls { hlsServer = startHls() } if app.Flv { startHTTPFlv(stream) } if app.Api { startAPI(stream) } startRtmp(stream, hlsServer) } } ================================================ FILE: parser/aac/parser.go ================================================ package aac import ( "fmt" "io" "github.com/gwuhaolin/livego/av" ) type mpegExtension struct { objectType byte sampleRate byte } type mpegCfgInfo struct { objectType byte sampleRate byte channel byte sbr byte ps byte frameLen byte exceptionLogTs int64 extension *mpegExtension } var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350} var ( specificBufInvalid = fmt.Errorf("audio mpegspecific error") audioBufInvalid = fmt.Errorf("audiodata invalid") ) const ( adtsHeaderLen = 7 ) type Parser struct { gettedSpecific bool adtsHeader []byte cfgInfo *mpegCfgInfo } func NewParser() *Parser { return &Parser{ gettedSpecific: false, cfgInfo: &mpegCfgInfo{}, adtsHeader: make([]byte, adtsHeaderLen), } } func (parser *Parser) specificInfo(src []byte) error { if len(src) < 2 { return specificBufInvalid } parser.gettedSpecific = true parser.cfgInfo.objectType = (src[0] >> 3) & 0xff parser.cfgInfo.sampleRate = ((src[0] & 0x07) << 1) | src[1]>>7 parser.cfgInfo.channel = (src[1] >> 3) & 0x0f return nil } func (parser *Parser) adts(src []byte, w io.Writer) error { if len(src) <= 0 || !parser.gettedSpecific { return audioBufInvalid } frameLen := uint16(len(src)) + 7 //first write adts header parser.adtsHeader[0] = 0xff parser.adtsHeader[1] = 0xf1 parser.adtsHeader[2] &= 0x00 parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.objectType-1)<<6 parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.sampleRate)<<2 parser.adtsHeader[3] &= 0x00 parser.adtsHeader[3] = parser.adtsHeader[3] | (parser.cfgInfo.channel<<2)<<4 parser.adtsHeader[3] = parser.adtsHeader[3] | byte((frameLen<<3)>>14) parser.adtsHeader[4] &= 0x00 parser.adtsHeader[4] = parser.adtsHeader[4] | byte((frameLen<<5)>>8) parser.adtsHeader[5] &= 0x00 parser.adtsHeader[5] = parser.adtsHeader[5] | byte(((frameLen<<13)>>13)<<5) parser.adtsHeader[5] = parser.adtsHeader[5] | (0x7C<<1)>>3 parser.adtsHeader[6] = 0xfc if _, err := w.Write(parser.adtsHeader[0:]); err != nil { return err } if _, err := w.Write(src); err != nil { return err } return nil } func (parser *Parser) SampleRate() int { rate := 44100 if parser.cfgInfo.sampleRate <= byte(len(aacRates)-1) { rate = aacRates[parser.cfgInfo.sampleRate] } return rate } func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (err error) { switch packetType { case av.AAC_SEQHDR: err = parser.specificInfo(b) case av.AAC_RAW: err = parser.adts(b, w) } return } ================================================ FILE: parser/h264/parser.go ================================================ package h264 import ( "bytes" "fmt" "io" ) const ( i_frame byte = 0 p_frame byte = 1 b_frame byte = 2 ) const ( nalu_type_not_define byte = 0 nalu_type_slice byte = 1 //slice_layer_without_partioning_rbsp() sliceheader nalu_type_dpa byte = 2 // slice_data_partition_a_layer_rbsp( ), slice_header nalu_type_dpb byte = 3 // slice_data_partition_b_layer_rbsp( ) nalu_type_dpc byte = 4 // slice_data_partition_c_layer_rbsp( ) nalu_type_idr byte = 5 // slice_layer_without_partitioning_rbsp( ),sliceheader nalu_type_sei byte = 6 //sei_rbsp( ) nalu_type_sps byte = 7 //seq_parameter_set_rbsp( ) nalu_type_pps byte = 8 //pic_parameter_set_rbsp( ) nalu_type_aud byte = 9 // access_unit_delimiter_rbsp( ) nalu_type_eoesq byte = 10 //end_of_seq_rbsp( ) nalu_type_eostream byte = 11 //end_of_stream_rbsp( ) nalu_type_filler byte = 12 //filler_data_rbsp( ) ) const ( naluBytesLen int = 4 maxSpsPpsLen int = 2 * 1024 ) var ( decDataNil = fmt.Errorf("dec buf is nil") spsDataError = fmt.Errorf("sps data error") ppsHeaderError = fmt.Errorf("pps header error") ppsDataError = fmt.Errorf("pps data error") naluHeaderInvalid = fmt.Errorf("nalu header invalid") videoDataInvalid = fmt.Errorf("video data not match") dataSizeNotMatch = fmt.Errorf("data size not match") naluBodyLenError = fmt.Errorf("nalu body len error") ) var startCode = []byte{0x00, 0x00, 0x00, 0x01} var naluAud = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0} type Parser struct { frameType byte specificInfo []byte pps *bytes.Buffer } type sequenceHeader struct { configVersion byte //8bits avcProfileIndication byte //8bits profileCompatility byte //8bits avcLevelIndication byte //8bits reserved1 byte //6bits naluLen byte //2bits reserved2 byte //3bits spsNum byte //5bits ppsNum byte //8bits spsLen int ppsLen int } func NewParser() *Parser { return &Parser{ pps: bytes.NewBuffer(make([]byte, maxSpsPpsLen)), } } //return value 1:sps, value2 :pps func (parser *Parser) parseSpecificInfo(src []byte) error { if len(src) < 9 { return decDataNil } sps := []byte{} pps := []byte{} var seq sequenceHeader seq.configVersion = src[0] seq.avcProfileIndication = src[1] seq.profileCompatility = src[2] seq.avcLevelIndication = src[3] seq.reserved1 = src[4] & 0xfc seq.naluLen = src[4]&0x03 + 1 seq.reserved2 = src[5] >> 5 //get sps seq.spsNum = src[5] & 0x1f seq.spsLen = int(src[6])<<8 | int(src[7]) if len(src[8:]) < seq.spsLen || seq.spsLen <= 0 { return spsDataError } sps = append(sps, startCode...) sps = append(sps, src[8:(8+seq.spsLen)]...) //get pps tmpBuf := src[(8 + seq.spsLen):] if len(tmpBuf) < 4 { return ppsHeaderError } seq.ppsNum = tmpBuf[0] seq.ppsLen = int(0)<<16 | int(tmpBuf[1])<<8 | int(tmpBuf[2]) if len(tmpBuf[3:]) < seq.ppsLen || seq.ppsLen <= 0 { return ppsDataError } pps = append(pps, startCode...) pps = append(pps, tmpBuf[3:]...) parser.specificInfo = append(parser.specificInfo, sps...) parser.specificInfo = append(parser.specificInfo, pps...) return nil } func (parser *Parser) isNaluHeader(src []byte) bool { if len(src) < naluBytesLen { return false } return src[0] == 0x00 && src[1] == 0x00 && src[2] == 0x00 && src[3] == 0x01 } func (parser *Parser) naluSize(src []byte) (int, error) { if len(src) < naluBytesLen { return 0, fmt.Errorf("nalusizedata invalid") } buf := src[:naluBytesLen] size := int(0) for i := 0; i < len(buf); i++ { size = size<<8 + int(buf[i]) } return size, nil } func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error { dataSize := len(src) if dataSize < naluBytesLen { return videoDataInvalid } parser.pps.Reset() _, err := w.Write(naluAud) if err != nil { return err } index := 0 nalLen := 0 hasSpsPps := false hasWriteSpsPps := false for dataSize > 0 { nalLen, err = parser.naluSize(src[index:]) if err != nil { return dataSizeNotMatch } index += naluBytesLen dataSize -= naluBytesLen if dataSize >= nalLen && len(src[index:]) >= nalLen && nalLen > 0 { nalType := src[index] & 0x1f switch nalType { case nalu_type_aud: case nalu_type_idr: if !hasWriteSpsPps { hasWriteSpsPps = true if !hasSpsPps { if _, err := w.Write(parser.specificInfo); err != nil { return err } } else { if _, err := w.Write(parser.pps.Bytes()); err != nil { return err } } } fallthrough case nalu_type_slice: fallthrough case nalu_type_sei: _, err := w.Write(startCode) if err != nil { return err } _, err = w.Write(src[index : index+nalLen]) if err != nil { return err } case nalu_type_sps: fallthrough case nalu_type_pps: hasSpsPps = true _, err := parser.pps.Write(startCode) if err != nil { return err } _, err = parser.pps.Write(src[index : index+nalLen]) if err != nil { return err } } index += nalLen dataSize -= nalLen } else { return naluBodyLenError } } return nil } func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err error) { switch isSeq { case true: err = parser.parseSpecificInfo(b) case false: // is annexb if parser.isNaluHeader(b) { _, err = w.Write(b) } else { err = parser.getAnnexbH264(b, w) } } return } ================================================ FILE: parser/h264/parser_test.go ================================================ package h264 import ( "bytes" "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestH264SeqDemux(t *testing.T) { at := assert.New(t) seq := []byte{ 0x01, 0x4d, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x01, 0x00, 0x04, 0x68, 0xde, 0x31, 0x12, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(seq, true, w) at.Equal(err, nil) at.Equal(d.specificInfo, []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12}) } func TestH264AnnexbDemux(t *testing.T) { at := assert.New(t) nalu := []byte{ 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(nalu, false, w) at.Equal(err, nil) at.Equal(w.Len(), 41) } func TestH264NalueSizeException(t *testing.T) { at := assert.New(t) nalu := []byte{ 0x00, 0x00, 0x10, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(nalu, false, w) at.Equal(err, fmt.Errorf("video data not match")) } func TestH264Mp4Demux(t *testing.T) { at := assert.New(t) nalu := []byte{ 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x04, 0x68, 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x02, 0x65, 0x23, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(nalu, false, w) at.Equal(err, nil) at.Equal(w.Len(), 47) at.Equal(w.Bytes(), []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23}) } func TestH264Mp4DemuxException1(t *testing.T) { at := assert.New(t) nalu := []byte{ 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(nalu, false, w) at.Equal(err, naluBodyLenError) } func TestH264Mp4DemuxException2(t *testing.T) { at := assert.New(t) nalu := []byte{ 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, } d := NewParser() w := bytes.NewBuffer(nil) err := d.Parse(nalu, false, w) at.Equal(err, naluBodyLenError) } ================================================ FILE: parser/mp3/parser.go ================================================ package mp3 import ( "fmt" ) type Parser struct { samplingFrequency int } func NewParser() *Parser { return &Parser{} } // sampling_frequency - indicates the sampling frequency, according to the following table. // '00' 44.1 kHz // '01' 48 kHz // '10' 32 kHz // '11' reserved var mp3Rates = []int{44100, 48000, 32000} var ( errMp3DataInvalid = fmt.Errorf("mp3data invalid") errIndexInvalid = fmt.Errorf("invalid rate index") ) func (parser *Parser) Parse(src []byte) error { if len(src) < 3 { return errMp3DataInvalid } index := (src[2] >> 2) & 0x3 if index <= byte(len(mp3Rates)-1) { parser.samplingFrequency = mp3Rates[index] return nil } return errIndexInvalid } func (parser *Parser) SampleRate() int { if parser.samplingFrequency == 0 { parser.samplingFrequency = 44100 } return parser.samplingFrequency } ================================================ FILE: parser/parser.go ================================================ package parser import ( "fmt" "io" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/parser/aac" "github.com/gwuhaolin/livego/parser/h264" "github.com/gwuhaolin/livego/parser/mp3" ) var ( errNoAudio = fmt.Errorf("demuxer no audio") ) type CodecParser struct { aac *aac.Parser mp3 *mp3.Parser h264 *h264.Parser } func NewCodecParser() *CodecParser { return &CodecParser{} } func (codeParser *CodecParser) SampleRate() (int, error) { if codeParser.aac == nil && codeParser.mp3 == nil { return 0, errNoAudio } if codeParser.aac != nil { return codeParser.aac.SampleRate(), nil } return codeParser.mp3.SampleRate(), nil } func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err error) { switch p.IsVideo { case true: f, ok := p.Header.(av.VideoPacketHeader) if ok { if f.CodecID() == av.VIDEO_H264 { if codeParser.h264 == nil { codeParser.h264 = h264.NewParser() } err = codeParser.h264.Parse(p.Data, f.IsSeq(), w) } } case false: f, ok := p.Header.(av.AudioPacketHeader) if ok { switch f.SoundFormat() { case av.SOUND_AAC: if codeParser.aac == nil { codeParser.aac = aac.NewParser() } err = codeParser.aac.Parse(p.Data, f.AACPacketType(), w) case av.SOUND_MP3: if codeParser.mp3 == nil { codeParser.mp3 = mp3.NewParser() } err = codeParser.mp3.Parse(p.Data) } } } return } ================================================ FILE: protocol/amf/amf.go ================================================ package amf import ( "fmt" "io" ) func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err error) { var v interface{} for { v, err = d.Decode(r, ver) if err != nil { break } ret = append(ret, v) } return } func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) { switch ver { case 0: return d.DecodeAmf0(r) case 3: return d.DecodeAmf3(r) } return nil, fmt.Errorf("decode amf: unsupported version %d", ver) } func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) { for _, v := range val { if _, err := e.Encode(w, v, ver); err != nil { return 0, err } } return 0, nil } func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error) { switch ver { case AMF0: return e.EncodeAmf0(w, val) case AMF3: return e.EncodeAmf3(w, val) } return 0, fmt.Errorf("encode amf: unsupported version %d", ver) } ================================================ FILE: protocol/amf/amf_test.go ================================================ package amf import ( "bytes" "fmt" "reflect" "testing" "time" ) func EncodeAndDecode(val interface{}, ver Version) (result interface{}, err error) { enc := new(Encoder) dec := new(Decoder) buf := new(bytes.Buffer) _, err = enc.Encode(buf, val, ver) if err != nil { return nil, fmt.Errorf("error in encode: %s", err) } result, err = dec.Decode(buf, ver) if err != nil { return nil, fmt.Errorf("error in decode: %s", err) } return } func Compare(val interface{}, ver Version, name string, t *testing.T) { result, err := EncodeAndDecode(val, ver) if err != nil { t.Errorf("%s: %s", name, err) } if !reflect.DeepEqual(val, result) { val_v := reflect.ValueOf(val) result_v := reflect.ValueOf(result) t.Errorf("%s: comparison failed between %+v (%s) and %+v (%s)", name, val, val_v.Type(), result, result_v.Type()) Dump("expected", val) Dump("got", result) } // if val != result { // t.Errorf("%s: comparison failed between %+v and %+v", name, val, result) // } } func TestAmf0Number(t *testing.T) { Compare(float64(3.14159), 0, "amf0 number float", t) Compare(float64(124567890), 0, "amf0 number high", t) Compare(float64(-34.2), 0, "amf0 number negative", t) } func TestAmf0String(t *testing.T) { Compare("a pup!", 0, "amf0 string simple", t) Compare("日本語", 0, "amf0 string utf8", t) } func TestAmf0Boolean(t *testing.T) { Compare(true, 0, "amf0 boolean true", t) Compare(false, 0, "amf0 boolean false", t) } func TestAmf0Null(t *testing.T) { Compare(nil, 0, "amf0 boolean nil", t) } func TestAmf0Object(t *testing.T) { obj := make(Object) obj["dog"] = "alfie" obj["coffee"] = true obj["drugs"] = false obj["pi"] = 3.14159 res, err := EncodeAndDecode(obj, 0) if err != nil { t.Errorf("amf0 object: %s", err) } result, ok := res.(Object) if ok != true { t.Errorf("amf0 object conversion failed") } if result["dog"] != "alfie" { t.Errorf("amf0 object string: comparison failed") } if result["coffee"] != true { t.Errorf("amf0 object true: comparison failed") } if result["drugs"] != false { t.Errorf("amf0 object false: comparison failed") } if result["pi"] != float64(3.14159) { t.Errorf("amf0 object float: comparison failed") } } func TestAmf0Array(t *testing.T) { arr := [5]float64{1, 2, 3, 4, 5} res, err := EncodeAndDecode(arr, 0) if err != nil { t.Errorf("amf0 object: %s", err) } result, ok := res.(Array) if ok != true { t.Errorf("amf0 array conversion failed") } for i := 0; i < len(arr); i++ { if arr[i] != result[i] { t.Errorf("amf0 array %d comparison failed: %v / %v", i, arr[i], result[i]) } } } func TestAmf3Integer(t *testing.T) { Compare(int32(0), 3, "amf3 integer zero", t) Compare(int32(1245), 3, "amf3 integer low", t) Compare(int32(123456), 3, "amf3 integer high", t) } func TestAmf3Double(t *testing.T) { Compare(float64(3.14159), 3, "amf3 double float", t) Compare(float64(1234567890), 3, "amf3 double high", t) Compare(float64(-12345), 3, "amf3 double negative", t) } func TestAmf3String(t *testing.T) { Compare("a pup!", 0, "amf0 string simple", t) Compare("日本語", 0, "amf0 string utf8", t) } func TestAmf3Boolean(t *testing.T) { Compare(true, 3, "amf3 boolean true", t) Compare(false, 3, "amf3 boolean false", t) } func TestAmf3Null(t *testing.T) { Compare(nil, 3, "amf3 boolean nil", t) } func TestAmf3Date(t *testing.T) { t1 := time.Unix(time.Now().Unix(), 0).UTC() // nanoseconds discarded t2 := time.Date(1983, 9, 4, 12, 4, 8, 0, time.UTC) Compare(t1, 3, "amf3 date now", t) Compare(t2, 3, "amf3 date earlier", t) } func TestAmf3Array(t *testing.T) { obj := make(Object) obj["key"] = "val" var arr Array arr = append(arr, "amf") arr = append(arr, float64(2)) arr = append(arr, -34.95) arr = append(arr, true) arr = append(arr, false) res, err := EncodeAndDecode(arr, 3) if err != nil { t.Errorf("amf3 object: %s", err) } result, ok := res.(Array) if ok != true { t.Errorf("amf3 array conversion failed: %+v", res) } for i := 0; i < len(arr); i++ { if arr[i] != result[i] { t.Errorf("amf3 array %d comparison failed: %v / %v", i, arr[i], result[i]) } } } func TestAmf3ByteArray(t *testing.T) { enc := new(Encoder) dec := new(Decoder) buf := new(bytes.Buffer) expect := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x00} enc.EncodeAmf3ByteArray(buf, expect, true) result, err := dec.DecodeAmf3ByteArray(buf, true) if err != nil { t.Errorf("err: %s", err) } if bytes.Compare(result, expect) != 0 { t.Errorf("expected: %+v, got %+v", expect, buf) } } ================================================ FILE: protocol/amf/const.go ================================================ package amf import ( "io" ) const ( AMF0 = 0x00 AMF3 = 0x03 ) const ( AMF0_NUMBER_MARKER = 0x00 AMF0_BOOLEAN_MARKER = 0x01 AMF0_STRING_MARKER = 0x02 AMF0_OBJECT_MARKER = 0x03 AMF0_MOVIECLIP_MARKER = 0x04 AMF0_NULL_MARKER = 0x05 AMF0_UNDEFINED_MARKER = 0x06 AMF0_REFERENCE_MARKER = 0x07 AMF0_ECMA_ARRAY_MARKER = 0x08 AMF0_OBJECT_END_MARKER = 0x09 AMF0_STRICT_ARRAY_MARKER = 0x0a AMF0_DATE_MARKER = 0x0b AMF0_LONG_STRING_MARKER = 0x0c AMF0_UNSUPPORTED_MARKER = 0x0d AMF0_RECORDSET_MARKER = 0x0e AMF0_XML_DOCUMENT_MARKER = 0x0f AMF0_TYPED_OBJECT_MARKER = 0x10 AMF0_ACMPLUS_OBJECT_MARKER = 0x11 ) const ( AMF0_BOOLEAN_FALSE = 0x00 AMF0_BOOLEAN_TRUE = 0x01 AMF0_STRING_MAX = 65535 AMF3_INTEGER_MAX = 536870911 ) const ( AMF3_UNDEFINED_MARKER = 0x00 AMF3_NULL_MARKER = 0x01 AMF3_FALSE_MARKER = 0x02 AMF3_TRUE_MARKER = 0x03 AMF3_INTEGER_MARKER = 0x04 AMF3_DOUBLE_MARKER = 0x05 AMF3_STRING_MARKER = 0x06 AMF3_XMLDOC_MARKER = 0x07 AMF3_DATE_MARKER = 0x08 AMF3_ARRAY_MARKER = 0x09 AMF3_OBJECT_MARKER = 0x0a AMF3_XMLSTRING_MARKER = 0x0b AMF3_BYTEARRAY_MARKER = 0x0c ) type ExternalHandler func(*Decoder, io.Reader) (interface{}, error) type Decoder struct { refCache []interface{} stringRefs []string objectRefs []interface{} traitRefs []Trait externalHandlers map[string]ExternalHandler } func NewDecoder() *Decoder { return &Decoder{ externalHandlers: make(map[string]ExternalHandler), } } func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandler) { d.externalHandlers[name] = f } type Encoder struct { } type Version uint8 type Array []interface{} type Object map[string]interface{} type TypedObject struct { Type string Object Object } type Trait struct { Type string Externalizable bool Dynamic bool Properties []string } func NewTrait() *Trait { return &Trait{} } func NewTypedObject() *TypedObject { return &TypedObject{ Type: "", Object: make(Object), } } ================================================ FILE: protocol/amf/decoder_amf0.go ================================================ package amf import ( "encoding/binary" "fmt" "io" ) // amf0 polymorphic router func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) { marker, err := ReadMarker(r) if err != nil { return nil, err } switch marker { case AMF0_NUMBER_MARKER: return d.DecodeAmf0Number(r, false) case AMF0_BOOLEAN_MARKER: return d.DecodeAmf0Boolean(r, false) case AMF0_STRING_MARKER: return d.DecodeAmf0String(r, false) case AMF0_OBJECT_MARKER: return d.DecodeAmf0Object(r, false) case AMF0_MOVIECLIP_MARKER: return nil, fmt.Errorf("decode amf0: unsupported type movieclip") case AMF0_NULL_MARKER: return d.DecodeAmf0Null(r, false) case AMF0_UNDEFINED_MARKER: return d.DecodeAmf0Undefined(r, false) case AMF0_REFERENCE_MARKER: return nil, fmt.Errorf("decode amf0: unsupported type reference") case AMF0_ECMA_ARRAY_MARKER: return d.DecodeAmf0EcmaArray(r, false) case AMF0_STRICT_ARRAY_MARKER: return d.DecodeAmf0StrictArray(r, false) case AMF0_DATE_MARKER: return d.DecodeAmf0Date(r, false) case AMF0_LONG_STRING_MARKER: return d.DecodeAmf0LongString(r, false) case AMF0_UNSUPPORTED_MARKER: return d.DecodeAmf0Unsupported(r, false) case AMF0_RECORDSET_MARKER: return nil, fmt.Errorf("decode amf0: unsupported type recordset") case AMF0_XML_DOCUMENT_MARKER: return d.DecodeAmf0XmlDocument(r, false) case AMF0_TYPED_OBJECT_MARKER: return d.DecodeAmf0TypedObject(r, false) case AMF0_ACMPLUS_OBJECT_MARKER: return d.DecodeAmf3(r) } return nil, fmt.Errorf("decode amf0: unsupported type %d", marker) } // marker: 1 byte 0x00 // format: 8 byte big endian float64 func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) { if err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil { return } err = binary.Read(r, binary.BigEndian, &result) if err != nil { return float64(0), fmt.Errorf("amf0 decode: unable to read number: %s", err) } return } // marker: 1 byte 0x01 // format: 1 byte, 0x00 = false, 0x01 = true func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) { if err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil { return } var b byte if b, err = ReadByte(r); err != nil { return } if b == AMF0_BOOLEAN_FALSE { return false, nil } else if b == AMF0_BOOLEAN_TRUE { return true, nil } return false, fmt.Errorf("decode amf0: unexpected value %v for boolean", b) } // marker: 1 byte 0x02 // format: // - 2 byte big endian uint16 header to determine size // - n (size) byte utf8 string func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) { if err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil { return } var length uint16 err = binary.Read(r, binary.BigEndian, &length) if err != nil { return "", fmt.Errorf("decode amf0: unable to decode string length: %s", err) } var bytes = make([]byte, length) if bytes, err = ReadBytes(r, int(length)); err != nil { return "", fmt.Errorf("decode amf0: unable to decode string value: %s", err) } return string(bytes), nil } // marker: 1 byte 0x03 // format: // - loop encoded string followed by encoded value // - terminated with empty string followed by 1 byte 0x09 func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) { if err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil { return nil, err } result := make(Object) d.refCache = append(d.refCache, result) for { key, err := d.DecodeAmf0String(r, false) if err != nil { return nil, err } if key == "" { if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil { return nil, fmt.Errorf("decode amf0: expected object end marker: %s", err) } break } value, err := d.DecodeAmf0(r) if err != nil { return nil, fmt.Errorf("decode amf0: unable to decode object value: %s", err) } result[key] = value } return result, nil } // marker: 1 byte 0x05 // no additional data func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) { err = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER) return } // marker: 1 byte 0x06 // no additional data func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) { err = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER) return } // marker: 1 byte 0x07 // format: 2 byte big endian uint16 /* func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) { if err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil { return nil, err } var err error var ref uint16 err = binary.Read(r, binary.BigEndian, &ref) if err != nil { return nil, fmt.Errorf("decode amf0: unable to decode reference id: %s", err) } if int(ref) > len(d.refCache) { return nil, fmt.Errorf("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache)) } result := d.refCache[ref] return result, nil } */ // marker: 1 byte 0x08 // format: // - 4 byte big endian uint32 with length of associative array // - normal object format: // - loop encoded string followed by encoded value // - terminated with empty string followed by 1 byte 0x09 func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (Object, error) { if err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil { return nil, err } var length uint32 err := binary.Read(r, binary.BigEndian, &length) result, err := d.DecodeAmf0Object(r, false) if err != nil { return nil, fmt.Errorf("decode amf0: unable to decode ecma array object: %s", err) } return result, nil } // marker: 1 byte 0x0a // format: // - 4 byte big endian uint32 to determine length of associative array // - n (length) encoded values func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result Array, err error) { if err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil { return nil, err } var length uint32 err = binary.Read(r, binary.BigEndian, &length) if err != nil { return nil, fmt.Errorf("decode amf0: unable to decode strict array length: %s", err) } d.refCache = append(d.refCache, result) for i := uint32(0); i < length; i++ { tmp, err := d.DecodeAmf0(r) if err != nil { return nil, fmt.Errorf("decode amf0: unable to decode strict array object: %s", err) } result = append(result, tmp) } return result, nil } // marker: 1 byte 0x0b // format: // - normal number format: // - 8 byte big endian float64 // - 2 byte unused func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) { if err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil { return } if result, err = d.DecodeAmf0Number(r, false); err != nil { return float64(0), fmt.Errorf("decode amf0: unable to decode float in date: %s", err) } if _, err = ReadBytes(r, 2); err != nil { return float64(0), fmt.Errorf("decode amf0: unable to read 2 trail bytes in date: %s", err) } return } // marker: 1 byte 0x0c // format: // - 4 byte big endian uint32 header to determine size // - n (size) byte utf8 string func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) { if err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil { return } var length uint32 err = binary.Read(r, binary.BigEndian, &length) if err != nil { return "", fmt.Errorf("decode amf0: unable to decode long string length: %s", err) } var bytes = make([]byte, length) if bytes, err = ReadBytes(r, int(length)); err != nil { return "", fmt.Errorf("decode amf0: unable to decode long string value: %s", err) } return string(bytes), nil } // marker: 1 byte 0x0d // no additional data func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) { err = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER) return } // marker: 1 byte 0x0f // format: // - normal long string format // - 4 byte big endian uint32 header to determine size // - n (size) byte utf8 string func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) { if err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil { return } return d.DecodeAmf0LongString(r, false) } // marker: 1 byte 0x10 // format: // - normal string format: // - 2 byte big endian uint16 header to determine size // - n (size) byte utf8 string // - normal object format: // - loop encoded string followed by encoded value // - terminated with empty string followed by 1 byte 0x09 func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (TypedObject, error) { result := *new(TypedObject) err := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER) if err != nil { return result, err } d.refCache = append(d.refCache, result) result.Type, err = d.DecodeAmf0String(r, false) if err != nil { return result, fmt.Errorf("decode amf0: typed object unable to determine type: %s", err) } result.Object, err = d.DecodeAmf0Object(r, false) if err != nil { return result, fmt.Errorf("decode amf0: typed object unable to determine object: %s", err) } return result, nil } ================================================ FILE: protocol/amf/decoder_amf0_test.go ================================================ package amf import ( "bytes" "testing" ) func TestDecodeAmf0Number(t *testing.T) { buf := bytes.NewReader([]byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}) expect := float64(1.2) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test number interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Number(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test number interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0Number(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0BooleanTrue(t *testing.T) { buf := bytes.NewReader([]byte{0x01, 0x01}) expect := true dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test boolean interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Boolean(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test boolean interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0Boolean(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0BooleanFalse(t *testing.T) { buf := bytes.NewReader([]byte{0x01, 0x00}) expect := false dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test boolean interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Boolean(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test boolean interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0Boolean(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0String(t *testing.T) { buf := bytes.NewReader([]byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f}) expect := "foo" dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test string interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0String(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test string interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0String(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0Object(t *testing.T) { buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } obj, ok := got.(Object) if ok != true { t.Errorf("expected result to cast to object") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } // Test object interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Object(buf, true) if err != nil { t.Errorf("%s", err) } obj, ok = got.(Object) if ok != true { t.Errorf("expected result to cast to object") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } // Test object interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0Object(buf, false) if err != nil { t.Errorf("%s", err) } obj, ok = got.(Object) if ok != true { t.Errorf("expected result to cast to object") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } } func TestDecodeAmf0Null(t *testing.T) { buf := bytes.NewReader([]byte{0x05}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } // Test null interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Null(buf, true) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } } func TestDecodeAmf0Undefined(t *testing.T) { buf := bytes.NewReader([]byte{0x06}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } // Test undefined interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Undefined(buf, true) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } } /* func TestDecodeReference(t *testing.T) { buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x09}) dec := &Decoder{} got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } obj, ok := got.(Object) if ok != true { t.Errorf("expected result to cast to object") } _, ok2 := obj["foo"].(Object) if ok2 != true { t.Errorf("expected foo value to cast to object") } } */ func TestDecodeAmf0EcmaArray(t *testing.T) { buf := bytes.NewReader([]byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } obj, ok := got.(Object) if ok != true { t.Errorf("expected result to cast to object") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } // Test ecma array interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0EcmaArray(buf, true) if err != nil { t.Errorf("%s", err) } obj, ok = got.(Object) if ok != true { t.Errorf("expected result to cast to object") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } // Test ecma array interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0EcmaArray(buf, false) if err != nil { t.Errorf("%s", err) } obj, ok = got.(Object) if ok != true { t.Errorf("expected result to cast to ecma array") } if obj["foo"] != "bar" { t.Errorf("expected {'foo'='bar'}, got %v", obj) } } func TestDecodeAmf0StrictArray(t *testing.T) { buf := bytes.NewReader([]byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } arr, ok := got.(Array) if ok != true { t.Errorf("expected result to cast to strict array") } if arr[0] != float64(5) { t.Errorf("expected array[0] to be 5, got %v", arr[0]) } if arr[1] != "foo" { t.Errorf("expected array[1] to be 'foo', got %v", arr[1]) } if arr[2] != nil { t.Errorf("expected array[2] to be nil, got %v", arr[2]) } // Test strict array interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0StrictArray(buf, true) if err != nil { t.Errorf("%s", err) } arr, ok = got.(Array) if ok != true { t.Errorf("expected result to cast to strict array") } if arr[0] != float64(5) { t.Errorf("expected array[0] to be 5, got %v", arr[0]) } if arr[1] != "foo" { t.Errorf("expected array[1] to be 'foo', got %v", arr[1]) } if arr[2] != nil { t.Errorf("expected array[2] to be nil, got %v", arr[2]) } // Test strict array interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0StrictArray(buf, false) if err != nil { t.Errorf("%s", err) } arr, ok = got.(Array) if ok != true { t.Errorf("expected result to cast to strict array") } if arr[0] != float64(5) { t.Errorf("expected array[0] to be 5, got %v", arr[0]) } if arr[1] != "foo" { t.Errorf("expected array[1] to be 'foo', got %v", arr[1]) } if arr[2] != nil { t.Errorf("expected array[2] to be nil, got %v", arr[2]) } } func TestDecodeAmf0Date(t *testing.T) { buf := bytes.NewReader([]byte{0x0b, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) expect := float64(5) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test date interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Date(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test date interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0Date(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0LongString(t *testing.T) { buf := bytes.NewReader([]byte{0x0c, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f}) expect := "foo" dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test long string interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0LongString(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test long string interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0LongString(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0Unsupported(t *testing.T) { buf := bytes.NewReader([]byte{0x0d}) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } // Test unsupported interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0Unsupported(buf, true) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } } func TestDecodeAmf0XmlDocument(t *testing.T) { buf := bytes.NewReader([]byte{0x0f, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f}) expect := "foo" dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test long string interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0XmlDocument(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } // Test long string interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0XmlDocument(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf0TypedObject(t *testing.T) { buf := bytes.NewReader([]byte{ 0x10, 0x00, 0x0F, 'o', 'r', 'g', '.', 'a', 'm', 'f', '.', 'A', 'S', 'C', 'l', 'a', 's', 's', 0x00, 0x03, 'b', 'a', 'z', 0x05, 0x00, 0x03, 'f', 'o', 'o', 0x02, 0x00, 0x03, 'b', 'a', 'r', 0x00, 0x00, 0x09, }) dec := &Decoder{} // Test main interface got, err := dec.DecodeAmf0(buf) if err != nil { t.Errorf("%s", err) } tobj, ok := got.(TypedObject) if ok != true { t.Errorf("expected result to cast to typed object, got %+v", tobj) } if tobj.Type != "org.amf.ASClass" { t.Errorf("expected typed object type to be 'class', got %v", tobj.Type) } if tobj.Object["foo"] != "bar" { t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"]) } if tobj.Object["baz"] != nil { t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"]) } // Test typed object interface with marker buf.Seek(0, 0) got, err = dec.DecodeAmf0TypedObject(buf, true) if err != nil { t.Errorf("%s", err) } tobj, ok = got.(TypedObject) if ok != true { t.Errorf("expected result to cast to typed object, got %+v", tobj) } if tobj.Type != "org.amf.ASClass" { t.Errorf("expected typed object type to be 'class', got %v", tobj.Type) } if tobj.Object["foo"] != "bar" { t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"]) } if tobj.Object["baz"] != nil { t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"]) } // Test typed object interface without marker buf.Seek(1, 0) got, err = dec.DecodeAmf0TypedObject(buf, false) if err != nil { t.Errorf("%s", err) } tobj, ok = got.(TypedObject) if ok != true { t.Errorf("expected result to cast to typed object, got %+v", tobj) } if tobj.Type != "org.amf.ASClass" { t.Errorf("expected typed object type to be 'class', got %v", tobj.Type) } if tobj.Object["foo"] != "bar" { t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"]) } if tobj.Object["baz"] != nil { t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"]) } } ================================================ FILE: protocol/amf/decoder_amf3.go ================================================ package amf import ( "encoding/binary" "fmt" "io" "time" ) // amf3 polymorphic router func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) { marker, err := ReadMarker(r) if err != nil { return nil, err } switch marker { case AMF3_UNDEFINED_MARKER: return d.DecodeAmf3Undefined(r, false) case AMF3_NULL_MARKER: return d.DecodeAmf3Null(r, false) case AMF3_FALSE_MARKER: return d.DecodeAmf3False(r, false) case AMF3_TRUE_MARKER: return d.DecodeAmf3True(r, false) case AMF3_INTEGER_MARKER: return d.DecodeAmf3Integer(r, false) case AMF3_DOUBLE_MARKER: return d.DecodeAmf3Double(r, false) case AMF3_STRING_MARKER: return d.DecodeAmf3String(r, false) case AMF3_XMLDOC_MARKER: return d.DecodeAmf3Xml(r, false) case AMF3_DATE_MARKER: return d.DecodeAmf3Date(r, false) case AMF3_ARRAY_MARKER: return d.DecodeAmf3Array(r, false) case AMF3_OBJECT_MARKER: return d.DecodeAmf3Object(r, false) case AMF3_XMLSTRING_MARKER: return d.DecodeAmf3Xml(r, false) case AMF3_BYTEARRAY_MARKER: return d.DecodeAmf3ByteArray(r, false) } return nil, fmt.Errorf("decode amf3: unsupported type %d", marker) } // marker: 1 byte 0x00 // no additional data func (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) { err = AssertMarker(r, decodeMarker, AMF3_UNDEFINED_MARKER) return } // marker: 1 byte 0x01 // no additional data func (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result interface{}, err error) { err = AssertMarker(r, decodeMarker, AMF3_NULL_MARKER) return } // marker: 1 byte 0x02 // no additional data func (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (result bool, err error) { err = AssertMarker(r, decodeMarker, AMF3_FALSE_MARKER) result = false return } // marker: 1 byte 0x03 // no additional data func (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result bool, err error) { err = AssertMarker(r, decodeMarker, AMF3_TRUE_MARKER) result = true return } // marker: 1 byte 0x04 func (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (result int32, err error) { if err = AssertMarker(r, decodeMarker, AMF3_INTEGER_MARKER); err != nil { return } var u29 uint32 u29, err = d.decodeU29(r) if err != nil { return } result = int32(u29) if result > 0xfffffff { result = int32(u29 - 0x20000000) } return } // marker: 1 byte 0x05 func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (result float64, err error) { if err = AssertMarker(r, decodeMarker, AMF3_DOUBLE_MARKER); err != nil { return } err = binary.Read(r, binary.BigEndian, &result) if err != nil { return float64(0), fmt.Errorf("amf3 decode: unable to read double: %s", err) } return } // marker: 1 byte 0x06 // format: // - u29 reference int. if reference, no more data. if not reference, // length value of bytes to read to complete string. func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result string, err error) { if err = AssertMarker(r, decodeMarker, AMF3_STRING_MARKER); err != nil { return } var isRef bool var refVal uint32 isRef, refVal, err = d.decodeReferenceInt(r) if err != nil { return "", fmt.Errorf("amf3 decode: unable to decode string reference and length: %s", err) } if isRef { result = d.stringRefs[refVal] return } buf := make([]byte, refVal) _, err = r.Read(buf) if err != nil { return "", fmt.Errorf("amf3 decode: unable to read string: %s", err) } result = string(buf) if result != "" { d.stringRefs = append(d.stringRefs, result) } return } // marker: 1 byte 0x08 // format: // - u29 reference int, if reference, no more data // - timestamp double func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Time, err error) { if err = AssertMarker(r, decodeMarker, AMF3_DATE_MARKER); err != nil { return } var isRef bool var refVal uint32 isRef, refVal, err = d.decodeReferenceInt(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode date reference and length: %s", err) } if isRef { res, ok := d.objectRefs[refVal].(time.Time) if ok != true { return result, fmt.Errorf("amf3 decode: unable to extract time from date object references") } return res, err } var u64 float64 err = binary.Read(r, binary.BigEndian, &u64) if err != nil { return result, fmt.Errorf("amf3 decode: unable to read double: %s", err) } result = time.Unix(int64(u64/1000), 0).UTC() d.objectRefs = append(d.objectRefs, result) return } // marker: 1 byte 0x09 // format: // - u29 reference int. if reference, no more data. // - string representing associative array if present // - n values (length of u29) func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array, err error) { if err = AssertMarker(r, decodeMarker, AMF3_ARRAY_MARKER); err != nil { return } var isRef bool var refVal uint32 isRef, refVal, err = d.decodeReferenceInt(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode array reference and length: %s", err) } if isRef { objRefId := refVal >> 1 res, ok := d.objectRefs[objRefId].(Array) if ok != true { return result, fmt.Errorf("amf3 decode: unable to extract array from object references") } return res, err } var key string key, err = d.DecodeAmf3String(r, false) if err != nil { return result, fmt.Errorf("amf3 decode: unable to read key for array: %s", err) } if key != "" { return result, fmt.Errorf("amf3 decode: array key is not empty, can't handle associative array") } for i := uint32(0); i < refVal; i++ { tmp, err := d.DecodeAmf3(r) if err != nil { return result, fmt.Errorf("amf3 decode: array element could not be decoded: %s", err) } result = append(result, tmp) } d.objectRefs = append(d.objectRefs, result) return } // marker: 1 byte 0x09 // format: oh dear god func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result interface{}, err error) { if err = AssertMarker(r, decodeMarker, AMF3_OBJECT_MARKER); err != nil { return nil, err } // decode the initial u29 isRef, refVal, err := d.decodeReferenceInt(r) if err != nil { return nil, fmt.Errorf("amf3 decode: unable to decode object reference and length: %s", err) } // if this is a object reference only, grab it and return it if isRef { objRefId := refVal >> 1 return d.objectRefs[objRefId], nil } // each type has traits that are cached, if the peer sent a reference // then we'll need to look it up and use it. var trait Trait traitIsRef := (refVal & 0x01) == 0 if traitIsRef { traitRef := refVal >> 1 trait = d.traitRefs[traitRef] } else { // build a new trait from what's left of the given u29 trait = *NewTrait() trait.Externalizable = (refVal & 0x02) != 0 trait.Dynamic = (refVal & 0x04) != 0 var cls string cls, err = d.DecodeAmf3String(r, false) if err != nil { return result, fmt.Errorf("amf3 decode: unable to read trait type for object: %s", err) } trait.Type = cls // traits have property keys, encoded as amf3 strings propLength := refVal >> 3 for i := uint32(0); i < propLength; i++ { tmp, err := d.DecodeAmf3String(r, false) if err != nil { return result, fmt.Errorf("amf3 decode: unable to read trait property for object: %s", err) } trait.Properties = append(trait.Properties, tmp) } d.traitRefs = append(d.traitRefs, trait) } d.objectRefs = append(d.objectRefs, result) // objects can be externalizable, meaning that the system has no concrete understanding of // their properties or how they are encoded. in that case, we need to find and delegate behavior // to the right object. if trait.Externalizable { switch trait.Type { case "DSA": // AsyncMessageExt result, err = d.decodeAsyncMessageExt(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode dsa: %s", err) } case "DSK": // AcknowledgeMessageExt result, err = d.decodeAcknowledgeMessageExt(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode dsk: %s", err) } case "flex.messaging.io.ArrayCollection": result, err = d.decodeArrayCollection(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode ac: %s", err) } // store an extra reference to array collection container d.objectRefs = append(d.objectRefs, result) default: fn, ok := d.externalHandlers[trait.Type] if ok { result, err = fn(d, r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to call external decoder for type %s: %s", trait.Type, err) } } else { return result, fmt.Errorf("amf3 decode: unable to decode external type %s, no handler", trait.Type) } } return result, err } var key string var val interface{} var obj Object obj = make(Object) // non-externalizable objects have property keys in traits, iterate through them // and add the read values to the object for _, key = range trait.Properties { val, err = d.DecodeAmf3(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode object property: %s", err) } obj[key] = val } // if an object is dynamic, it can have extra key/value data at the end. in this case, // read keys until we get an empty one. if trait.Dynamic { for { key, err = d.DecodeAmf3String(r, false) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode dynamic key: %s", err) } if key == "" { break } val, err = d.DecodeAmf3(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode dynamic value: %s", err) } obj[key] = val } } result = obj return } // marker: 1 byte 0x07 or 0x0b // format: // - u29 reference int. if reference, no more data. if not reference, // length value of bytes to read to complete string. func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string, err error) { if decodeMarker { var marker byte marker, err = ReadMarker(r) if err != nil { return "", err } if (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) { return "", fmt.Errorf("decode assert marker failed: expected %v or %v, got %v", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker) } } var isRef bool var refVal uint32 isRef, refVal, err = d.decodeReferenceInt(r) if err != nil { return "", fmt.Errorf("amf3 decode: unable to decode xml reference and length: %s", err) } if isRef { var ok bool buf := d.objectRefs[refVal] result, ok = buf.(string) if ok != true { return "", fmt.Errorf("amf3 decode: cannot coerce object reference into xml string") } return } buf := make([]byte, refVal) _, err = r.Read(buf) if err != nil { return "", fmt.Errorf("amf3 decode: unable to read xml string: %s", err) } result = string(buf) if result != "" { d.objectRefs = append(d.objectRefs, result) } return } // marker: 1 byte 0x0c // format: // - u29 reference int. if reference, no more data. if not reference, // length value of bytes to read. func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []byte, err error) { if err = AssertMarker(r, decodeMarker, AMF3_BYTEARRAY_MARKER); err != nil { return } var isRef bool var refVal uint32 isRef, refVal, err = d.decodeReferenceInt(r) if err != nil { return result, fmt.Errorf("amf3 decode: unable to decode byte array reference and length: %s", err) } if isRef { var ok bool result, ok = d.objectRefs[refVal].([]byte) if ok != true { return result, fmt.Errorf("amf3 decode: unable to convert object ref to bytes") } return } result = make([]byte, refVal) _, err = r.Read(result) if err != nil { return result, fmt.Errorf("amf3 decode: unable to read bytearray: %s", err) } d.objectRefs = append(d.objectRefs, result) return } func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) { var b byte for i := 0; i < 3; i++ { b, err = ReadByte(r) if err != nil { return } result = (result << 7) + uint32(b&0x7F) if (b & 0x80) == 0 { return } } b, err = ReadByte(r) if err != nil { return } result = ((result << 8) + uint32(b)) return } func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) { u29, err := d.decodeU29(r) if err != nil { return false, 0, fmt.Errorf("amf3 decode: unable to decode reference int: %s", err) } isRef = u29&0x01 == 0 refVal = u29 >> 1 return } ================================================ FILE: protocol/amf/decoder_amf3_external.go ================================================ package amf import ( "fmt" "io" "math" ) // Abstract external boilerplate func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error) { result = make(Object) if err = d.decodeExternal(r, &result, []string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"}, []string{"clientIdBytes", "messageIdBytes"}); err != nil { return result, fmt.Errorf("unable to decode abstract external: %s", err) } return } // DSA func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error) { return d.decodeAsyncMessage(r) } func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) { result, err = d.decodeAbstractMessage(r) if err != nil { return result, fmt.Errorf("unable to decode abstract for async: %s", err) } if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil { return result, fmt.Errorf("unable to decode async external: %s", err) } return } // DSK func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err error) { return d.decodeAcknowledgeMessage(r) } func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) { result, err = d.decodeAsyncMessage(r) if err != nil { return result, fmt.Errorf("unable to decode async for ack: %s", err) } if err = d.decodeExternal(r, &result); err != nil { return result, fmt.Errorf("unable to decode ack external: %s", err) } return } // flex.messaging.io.ArrayCollection func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) { result, err := d.DecodeAmf3(r) if err != nil { return result, fmt.Errorf("cannot decode child of array collection: %s", err) } return result, nil } func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string) (err error) { var flagSet []uint8 var reservedPosition uint8 var fieldNames []string flagSet, err = readFlags(r) if err != nil { return fmt.Errorf("unable to read flags: %s", err) } for i, flags := range flagSet { if i < len(fieldSets) { fieldNames = fieldSets[i] } else { fieldNames = []string{} } reservedPosition = uint8(len(fieldNames)) for p, field := range fieldNames { flagBit := uint8(math.Exp2(float64(p))) if (flags & flagBit) != 0 { tmp, err := d.DecodeAmf3(r) if err != nil { return fmt.Errorf("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err) } (*obj)[field] = tmp } } if (flags >> reservedPosition) != 0 { for j := reservedPosition; j < 6; j++ { if ((flags >> j) & 0x01) != 0 { field := fmt.Sprintf("extra_%d_%d", i, j) tmp, err := d.DecodeAmf3(r) if err != nil { return fmt.Errorf("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err) } (*obj)[field] = tmp } } } } return } func readFlags(r io.Reader) (result []uint8, err error) { for { flag, err := ReadByte(r) if err != nil { return result, fmt.Errorf("unable to read flags: %s", err) } result = append(result, flag) if (flag & 0x80) == 0 { break } } return } ================================================ FILE: protocol/amf/decoder_amf3_test.go ================================================ package amf import ( "bytes" "testing" ) type u29TestCase struct { value uint32 expect []byte } var u29TestCases = []u29TestCase{ {1, []byte{0x01}}, {2, []byte{0x02}}, {127, []byte{0x7F}}, {128, []byte{0x81, 0x00}}, {255, []byte{0x81, 0x7F}}, {256, []byte{0x82, 0x00}}, {0x3FFF, []byte{0xFF, 0x7F}}, {0x4000, []byte{0x81, 0x80, 0x00}}, {0x7FFF, []byte{0x81, 0xFF, 0x7F}}, {0x8000, []byte{0x82, 0x80, 0x00}}, {0x1FFFFF, []byte{0xFF, 0xFF, 0x7F}}, {0x200000, []byte{0x80, 0xC0, 0x80, 0x00}}, {0x3FFFFF, []byte{0x80, 0xFF, 0xFF, 0xFF}}, {0x400000, []byte{0x81, 0x80, 0x80, 0x00}}, {0x0FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF}}, } func TestDecodeAmf3Undefined(t *testing.T) { buf := bytes.NewReader([]byte{0x00}) dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } } func TestDecodeAmf3Null(t *testing.T) { buf := bytes.NewReader([]byte{0x01}) dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if got != nil { t.Errorf("expect nil got %v", got) } } func TestDecodeAmf3False(t *testing.T) { buf := bytes.NewReader([]byte{0x02}) expect := false dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf3True(t *testing.T) { buf := bytes.NewReader([]byte{0x03}) expect := true dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeU29(t *testing.T) { dec := new(Decoder) for _, tc := range u29TestCases { buf := bytes.NewBuffer(tc.expect) n, err := dec.decodeU29(buf) if err != nil { t.Errorf("DecodeAmf3Integer error: %s", err) } if n != tc.value { t.Errorf("DecodeAmf3Integer expect n %x got %x", tc.value, n) } } } func TestDecodeAmf3Integer(t *testing.T) { dec := new(Decoder) buf := bytes.NewReader([]byte{0x04, 0xFF, 0xFF, 0x7F}) expect := int32(2097151) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } buf.Seek(0, 0) got, err = dec.DecodeAmf3Integer(buf, true) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } buf.Seek(1, 0) got, err = dec.DecodeAmf3Integer(buf, false) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf3Double(t *testing.T) { buf := bytes.NewReader([]byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}) expect := float64(1.2) dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf3String(t *testing.T) { buf := bytes.NewReader([]byte{0x06, 0x07, 'f', 'o', 'o'}) expect := "foo" dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("%s", err) } if expect != got { t.Errorf("expect %v got %v", expect, got) } } func TestDecodeAmf3Array(t *testing.T) { buf := bytes.NewReader([]byte{0x09, 0x13, 0x01, 0x06, 0x03, '1', 0x06, 0x03, '2', 0x06, 0x03, '3', 0x06, 0x03, '4', 0x06, 0x03, '5', 0x06, 0x03, '6', 0x06, 0x03, '7', 0x06, 0x03, '8', 0x06, 0x03, '9', }) dec := new(Decoder) expect := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"} got, err := dec.DecodeAmf3Array(buf, true) if err != nil { t.Errorf("err: %s", err) } for i, v := range expect { if got[i] != v { t.Errorf("expected array element %d to be %v, got %v", i, v, got[i]) } } } func TestDecodeAmf3Object(t *testing.T) { buf := bytes.NewReader([]byte{ 0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a', 'm', 'f', '.', 'A', 'S', 'C', 'l', 'a', 's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f', 'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r', }) dec := new(Decoder) got, err := dec.DecodeAmf3(buf) if err != nil { t.Errorf("err: %s", err) } to, ok := got.(Object) if ok != true { t.Error("unable to cast object as typed object") } if to["foo"] != "bar" { t.Errorf("expected foo to be bar, got: %+v", to["foo"]) } if to["baz"] != nil { t.Errorf("expected baz to be nil, got: %+v", to["baz"]) } } ================================================ FILE: protocol/amf/encoder_amf0.go ================================================ package amf import ( "encoding/binary" "fmt" "io" "reflect" ) // amf0 polymorphic router func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) { if val == nil { return e.EncodeAmf0Null(w, true) } v := reflect.ValueOf(val) if !v.IsValid() { return e.EncodeAmf0Null(w, true) } switch v.Kind() { case reflect.String: str := v.String() if len(str) <= AMF0_STRING_MAX { return e.EncodeAmf0String(w, str, true) } else { return e.EncodeAmf0LongString(w, str, true) } case reflect.Bool: return e.EncodeAmf0Boolean(w, v.Bool(), true) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return e.EncodeAmf0Number(w, float64(v.Int()), true) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return e.EncodeAmf0Number(w, float64(v.Uint()), true) case reflect.Float32, reflect.Float64: return e.EncodeAmf0Number(w, float64(v.Float()), true) case reflect.Array, reflect.Slice: length := v.Len() arr := make(Array, length) for i := 0; i < length; i++ { arr[i] = v.Index(int(i)).Interface() } return e.EncodeAmf0StrictArray(w, arr, true) case reflect.Map: obj, ok := val.(Object) if ok != true { return 0, fmt.Errorf("encode amf0: unable to create object from map") } return e.EncodeAmf0Object(w, obj, true) } if _, ok := val.(TypedObject); ok { return 0, fmt.Errorf("encode amf0: unsupported type typed object") } return 0, fmt.Errorf("encode amf0: unsupported type %s", v.Type()) } // marker: 1 byte 0x00 // format: 8 byte big endian float64 func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_NUMBER_MARKER); err != nil { return } n += 1 } err = binary.Write(w, binary.BigEndian, &val) if err != nil { return } n += 8 return } // marker: 1 byte 0x01 // format: 1 byte, 0x00 = false, 0x01 = true func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_BOOLEAN_MARKER); err != nil { return } n += 1 } var m int buf := make([]byte, 1) if val { buf[0] = AMF0_BOOLEAN_TRUE } else { buf[0] = AMF0_BOOLEAN_FALSE } m, err = w.Write(buf) if err != nil { return } n += m return } // marker: 1 byte 0x02 // format: // - 2 byte big endian uint16 header to determine size // - n (size) byte utf8 string func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_STRING_MARKER); err != nil { return } n += 1 } var m int length := uint16(len(val)) err = binary.Write(w, binary.BigEndian, length) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode string length: %s", err) } n += 2 m, err = w.Write([]byte(val)) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode string value: %s", err) } n += m return } // marker: 1 byte 0x03 // format: // - loop encoded string followed by encoded value // - terminated with empty string followed by 1 byte 0x09 func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_OBJECT_MARKER); err != nil { return } n += 1 } var m int for k, v := range val { m, err = e.EncodeAmf0String(w, k, false) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode object key: %s", err) } n += m m, err = e.EncodeAmf0(w, v) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode object value: %s", err) } n += m } m, err = e.EncodeAmf0String(w, "", false) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode object empty string: %s", err) } n += m err = WriteMarker(w, AMF0_OBJECT_END_MARKER) if err != nil { return n, fmt.Errorf("encode amf0: unable to object end marker: %s", err) } n += 1 return } // marker: 1 byte 0x05 // no additional data func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_NULL_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x06 // no additional data func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_UNDEFINED_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x08 // format: // - 4 byte big endian uint32 with length of associative array // - normal object format: // - loop encoded string followed by encoded value // - terminated with empty string followed by 1 byte 0x09 func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_ECMA_ARRAY_MARKER); err != nil { return } n += 1 } var m int length := uint32(len(val)) err = binary.Write(w, binary.BigEndian, length) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode ecma array length: %s", err) } n += 4 m, err = e.EncodeAmf0Object(w, val, false) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode ecma array object: %s", err) } n += m return } // marker: 1 byte 0x0a // format: // - 4 byte big endian uint32 to determine length of associative array // - n (length) encoded values func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_STRICT_ARRAY_MARKER); err != nil { return } n += 1 } var m int length := uint32(len(val)) err = binary.Write(w, binary.BigEndian, length) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode strict array length: %s", err) } n += 4 for _, v := range val { m, err = e.EncodeAmf0(w, v) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode strict array element: %s", err) } n += m } return } // marker: 1 byte 0x0c // format: // - 4 byte big endian uint32 header to determine size // - n (size) byte utf8 string func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_LONG_STRING_MARKER); err != nil { return } n += 1 } var m int length := uint32(len(val)) err = binary.Write(w, binary.BigEndian, length) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode long string length: %s", err) } n += 4 m, err = w.Write([]byte(val)) if err != nil { return n, fmt.Errorf("encode amf0: unable to encode long string value: %s", err) } n += m return } // marker: 1 byte 0x0d // no additional data func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF0_UNSUPPORTED_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x11 func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error { return WriteMarker(w, AMF0_ACMPLUS_OBJECT_MARKER) } ================================================ FILE: protocol/amf/encoder_amf0_test.go ================================================ package amf import ( "bytes" "encoding/binary" "testing" ) func TestEncodeAmf0Number(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33} enc := new(Encoder) n, err := enc.EncodeAmf0(buf, float64(1.2)) if err != nil { t.Errorf("%s", err) } if n != 9 { t.Errorf("expected to write 9 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0BooleanTrue(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x01, 0x01} enc := new(Encoder) n, err := enc.EncodeAmf0(buf, true) if err != nil { t.Errorf("%s", err) } if n != 2 { t.Errorf("expected to write 2 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0BooleanFalse(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x01, 0x00} enc := new(Encoder) n, err := enc.EncodeAmf0(buf, false) if err != nil { t.Errorf("%s", err) } if n != 2 { t.Errorf("expected to write 2 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0String(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f} enc := new(Encoder) n, err := enc.EncodeAmf0(buf, "foo") if err != nil { t.Errorf("%s", err) } if n != 6 { t.Errorf("expected to write 6 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0Object(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09} enc := new(Encoder) obj := make(Object) obj["foo"] = "bar" n, err := enc.EncodeAmf0(buf, obj) if err != nil { t.Errorf("%s", err) } if n != 15 { t.Errorf("expected to write 15 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0EcmaArray(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09} enc := new(Encoder) obj := make(Object) obj["foo"] = "bar" _, err := enc.EncodeAmf0EcmaArray(buf, obj, true) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0StrictArray(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05} enc := new(Encoder) arr := make(Array, 3) arr[0] = float64(5) arr[1] = "foo" arr[2] = nil _, err := enc.EncodeAmf0StrictArray(buf, arr, true) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0Null(t *testing.T) { buf := new(bytes.Buffer) expect := []byte{0x05} enc := new(Encoder) n, err := enc.EncodeAmf0(buf, nil) if err != nil { t.Errorf("%s", err) } if n != 1 { t.Errorf("expected to write 1 byte, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf0LongString(t *testing.T) { buf := new(bytes.Buffer) testBytes := []byte("12345678") tbuf := new(bytes.Buffer) for i := 0; i < 65536; i++ { tbuf.Write(testBytes) } enc := new(Encoder) _, err := enc.EncodeAmf0(buf, string(tbuf.Bytes())) if err != nil { t.Errorf("%s", err) } mbuf := make([]byte, 1) _, err = buf.Read(mbuf) if err != nil { t.Errorf("error reading header") } if mbuf[0] != 0x0c { t.Errorf("marker mismatch") } var length uint32 err = binary.Read(buf, binary.BigEndian, &length) if err != nil { t.Errorf("error reading buffer") } if length != (65536 * 8) { t.Errorf("expected length to be %d, got %d", (65536 * 8), length) } tmpBuf := make([]byte, 8) counter := 0 for buf.Len() > 0 { n, err := buf.Read(tmpBuf) if err != nil { t.Fatalf("test long string result check, read data(%d) error: %s, n: %d", counter, err, n) } if n != 8 { t.Fatalf("test long string result check, read data(%d) n: %d", counter, n) } if !bytes.Equal(testBytes, tmpBuf) { t.Fatalf("test long string result check, read data % x", tmpBuf) } counter++ } } ================================================ FILE: protocol/amf/encoder_amf3.go ================================================ package amf import ( "encoding/binary" "fmt" "io" "reflect" "sort" "time" ) // amf3 polymorphic router func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) { if val == nil { return e.EncodeAmf3Null(w, true) } v := reflect.ValueOf(val) if !v.IsValid() { return e.EncodeAmf3Null(w, true) } switch v.Kind() { case reflect.String: return e.EncodeAmf3String(w, v.String(), true) case reflect.Bool: if v.Bool() { return e.EncodeAmf3True(w, true) } else { return e.EncodeAmf3False(w, true) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: n := v.Int() if n >= 0 && n <= AMF3_INTEGER_MAX { return e.EncodeAmf3Integer(w, uint32(n), true) } else { return e.EncodeAmf3Double(w, float64(n), true) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: n := v.Uint() if n <= AMF3_INTEGER_MAX { return e.EncodeAmf3Integer(w, uint32(n), true) } else { return e.EncodeAmf3Double(w, float64(n), true) } case reflect.Int64: return e.EncodeAmf3Double(w, float64(v.Int()), true) case reflect.Uint64: return e.EncodeAmf3Double(w, float64(v.Uint()), true) case reflect.Float32, reflect.Float64: return e.EncodeAmf3Double(w, float64(v.Float()), true) case reflect.Array, reflect.Slice: length := v.Len() arr := make(Array, length) for i := 0; i < length; i++ { arr[i] = v.Index(int(i)).Interface() } return e.EncodeAmf3Array(w, arr, true) case reflect.Map: obj, ok := val.(Object) if ok != true { return 0, fmt.Errorf("encode amf3: unable to create object from map") } to := *new(TypedObject) to.Object = obj return e.EncodeAmf3Object(w, to, true) } if tm, ok := val.(time.Time); ok { return e.EncodeAmf3Date(w, tm, true) } if to, ok := val.(TypedObject); ok { return e.EncodeAmf3Object(w, to, true) } return 0, fmt.Errorf("encode amf3: unsupported type %s", v.Type()) } // marker: 1 byte 0x00 // no additional data func (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_UNDEFINED_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x01 // no additional data func (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_NULL_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x02 // no additional data func (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_FALSE_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x03 // no additional data func (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_TRUE_MARKER); err != nil { return } n += 1 } return } // marker: 1 byte 0x04 func (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_INTEGER_MARKER); err != nil { return } n += 1 } var m int m, err = e.encodeAmf3Uint29(w, val) if err != nil { return } n += m return } // marker: 1 byte 0x05 func (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_DOUBLE_MARKER); err != nil { return } n += 1 } err = binary.Write(w, binary.BigEndian, &val) if err != nil { return } n += 8 return } // marker: 1 byte 0x06 // format: // - u29 reference int. if reference, no more data. if not reference, // length value of bytes to read to complete string. func (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_STRING_MARKER); err != nil { return } n += 1 } var m int m, err = e.encodeAmf3Utf8(w, val) if err != nil { return } n += m return } // marker: 1 byte 0x08 // format: // - u29 reference int, if reference, no more data // - timestamp double func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_DATE_MARKER); err != nil { return } n += 1 } if err = WriteMarker(w, 0x01); err != nil { return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err) } n += 1 u64 := float64(val.Unix()) * 1000.0 err = binary.Write(w, binary.BigEndian, &u64) if err != nil { return n, fmt.Errorf("amf3 encode: unable to write date double: %s", err) } n += 8 return } // marker: 1 byte 0x09 // format: // - u29 reference int. if reference, no more data. // - string representing associative array if present // - n values (length of u29) func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_ARRAY_MARKER); err != nil { return } n += 1 } var m int length := uint32(len(val)) u29 := uint32(length<<1) | 0x01 m, err = e.encodeAmf3Uint29(w, u29) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err) } n += m m, err = e.encodeAmf3Utf8(w, "") if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode empty string for array: %s", err) } n += m for _, v := range val { m, err := e.EncodeAmf3(w, v) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode array element: %s", err) } n += m } return } // marker: 1 byte 0x0a // format: ugh func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_OBJECT_MARKER); err != nil { return } n += 1 } m := 0 trait := *NewTrait() trait.Type = val.Type trait.Dynamic = false trait.Externalizable = false for k, _ := range val.Object { trait.Properties = append(trait.Properties, k) } sort.Strings(trait.Properties) var u29 uint32 = 0x03 if trait.Dynamic { u29 |= 0x02 << 2 } if trait.Externalizable { u29 |= 0x01 << 2 } u29 |= uint32(len(trait.Properties)) << 4 m, err = e.encodeAmf3Uint29(w, u29) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode trait header for object: %s", err) } n += m m, err = e.encodeAmf3Utf8(w, trait.Type) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode trait type for object: %s", err) } n += m for _, prop := range trait.Properties { m, err = e.encodeAmf3Utf8(w, prop) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode trait property for object: %s", err) } n += m } if trait.Externalizable { return n, fmt.Errorf("amf3 encode: cannot encode externalizable object") } for _, prop := range trait.Properties { m, err = e.EncodeAmf3(w, val.Object[prop]) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode sealed object value: %s", err) } n += m } if trait.Dynamic { for k, v := range val.Object { var foundProp bool = false for _, prop := range trait.Properties { if prop == k { foundProp = true break } } if foundProp != true { m, err = e.encodeAmf3Utf8(w, k) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode dynamic object property key: %s", err) } n += m m, err = e.EncodeAmf3(w, v) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode dynamic object value: %s", err) } n += m } m, err = e.encodeAmf3Utf8(w, "") if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode dynamic object ending marker string: %s", err) } n += m } } return } // marker: 1 byte 0x0c // format: // - u29 reference int. if reference, no more data. if not reference, // length value of bytes to read . func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMarker bool) (n int, err error) { if encodeMarker { if err = WriteMarker(w, AMF3_BYTEARRAY_MARKER); err != nil { return } n += 1 } var m int length := uint32(len(val)) u29 := (length << 1) | 1 m, err = e.encodeAmf3Uint29(w, u29) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode u29 for bytearray: %s", err) } n += m m, err = w.Write(val) if err != nil { return n, fmt.Errorf("encode amf3: unable to encode bytearray value: %s", err) } n += m return } func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err error) { length := uint32(len(val)) u29 := uint32(length<<1) | 0x01 var m int m, err = e.encodeAmf3Uint29(w, u29) if err != nil { return n, fmt.Errorf("amf3 encode: cannot encode u29 for string: %s", err) } n += m m, err = w.Write([]byte(val)) if err != nil { return n, fmt.Errorf("encode amf3: unable to encode string value: %s", err) } n += m return } func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err error) { if val <= 0x0000007F { err = WriteByte(w, byte(val)) if err == nil { n += 1 } } else if val <= 0x00003FFF { n, err = w.Write([]byte{byte(val>>7 | 0x80), byte(val & 0x7F)}) } else if val <= 0x001FFFFF { n, err = w.Write([]byte{byte(val>>14 | 0x80), byte(val>>7&0x7F | 0x80), byte(val & 0x7F)}) } else if val <= 0x1FFFFFFF { n, err = w.Write([]byte{byte(val>>22 | 0x80), byte(val>>15&0x7F | 0x80), byte(val>>8&0x7F | 0x80), byte(val)}) } else { return n, fmt.Errorf("amf3 encode: cannot encode u29 with value %d (out of range)", val) } return } ================================================ FILE: protocol/amf/encoder_amf3_test.go ================================================ package amf import ( "bytes" "testing" ) func TestEncodeAmf3EmptyString(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x01} _, err := enc.EncodeAmf3String(buf, "", false) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Undefined(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x00} _, err := enc.EncodeAmf3Undefined(buf, true) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Null(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x01} _, err := enc.EncodeAmf3(buf, nil) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3False(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x02} _, err := enc.EncodeAmf3(buf, false) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3True(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x03} _, err := enc.EncodeAmf3(buf, true) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Integer(t *testing.T) { enc := new(Encoder) for _, tc := range u29TestCases { buf := new(bytes.Buffer) _, err := enc.EncodeAmf3Integer(buf, tc.value, false) if err != nil { t.Errorf("EncodeAmf3Integer error: %s", err) } got := buf.Bytes() if !bytes.Equal(tc.expect, got) { t.Errorf("EncodeAmf3Integer expect n %x got %x", tc.value, got) } } buf := new(bytes.Buffer) expect := []byte{0x04, 0x80, 0xFF, 0xFF, 0xFF} n, err := enc.EncodeAmf3(buf, uint32(4194303)) if err != nil { t.Errorf("%s", err) } if n != 5 { t.Errorf("expected to write 5 bytes, actual %d", n) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Double(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33} _, err := enc.EncodeAmf3(buf, float64(1.2)) if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3String(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x06, 0x07, 'f', 'o', 'o'} _, err := enc.EncodeAmf3(buf, "foo") if err != nil { t.Errorf("%s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Array(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{0x09, 0x13, 0x01, 0x06, 0x03, '1', 0x06, 0x03, '2', 0x06, 0x03, '3', 0x06, 0x03, '4', 0x06, 0x03, '5', 0x06, 0x03, '6', 0x06, 0x03, '7', 0x06, 0x03, '8', 0x06, 0x03, '9', } arr := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"} _, err := enc.EncodeAmf3(buf, arr) if err != nil { t.Errorf("err: %s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) } } func TestEncodeAmf3Object(t *testing.T) { enc := new(Encoder) buf := new(bytes.Buffer) expect := []byte{ 0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a', 'm', 'f', '.', 'A', 'S', 'C', 'l', 'a', 's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f', 'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r', } to := *NewTypedObject() to.Type = "org.amf.ASClass" to.Object["foo"] = "bar" to.Object["baz"] = nil _, err := enc.EncodeAmf3(buf, to) if err != nil { t.Errorf("err: %s", err) } if bytes.Compare(buf.Bytes(), expect) != 0 { t.Errorf("expected buffer:\n%#v\ngot:\n%#v", expect, buf.Bytes()) } } ================================================ FILE: protocol/amf/metadata.go ================================================ package amf import ( "bytes" "fmt" log "github.com/sirupsen/logrus" ) const ( ADD = 0x0 DEL = 0x3 ) const ( SetDataFrame string = "@setDataFrame" OnMetaData string = "onMetaData" ) var setFrameFrame []byte func init() { b := bytes.NewBuffer(nil) encoder := &Encoder{} if _, err := encoder.Encode(b, SetDataFrame, AMF0); err != nil { log.Fatal(err) } setFrameFrame = b.Bytes() } func MetaDataReform(p []byte, flag uint8) ([]byte, error) { r := bytes.NewReader(p) decoder := &Decoder{} switch flag { case ADD: v, err := decoder.Decode(r, AMF0) if err != nil { return nil, err } switch v.(type) { case string: vv := v.(string) if vv != SetDataFrame { tmplen := len(setFrameFrame) b := make([]byte, tmplen+len(p)) copy(b, setFrameFrame) copy(b[tmplen:], p) p = b } default: return nil, fmt.Errorf("setFrameFrame error") } case DEL: v, err := decoder.Decode(r, AMF0) if err != nil { return nil, err } switch v.(type) { case string: vv := v.(string) if vv == SetDataFrame { p = p[len(setFrameFrame):] } default: return nil, fmt.Errorf("metadata error") } default: return nil, fmt.Errorf("invalid flag:%d", flag) } return p, nil } ================================================ FILE: protocol/amf/util.go ================================================ package amf import ( "encoding/json" "fmt" "io" ) func DumpBytes(label string, buf []byte, size int) { fmt.Printf("Dumping %s (%d bytes):\n", label, size) for i := 0; i < size; i++ { fmt.Printf("0x%02x ", buf[i]) } fmt.Printf("\n") } func Dump(label string, val interface{}) error { json, err := json.MarshalIndent(val, "", " ") if err != nil { return fmt.Errorf("Error dumping %s: %s", label, err) } fmt.Printf("Dumping %s:\n%s\n", label, json) return nil } func WriteByte(w io.Writer, b byte) (err error) { bytes := make([]byte, 1) bytes[0] = b _, err = WriteBytes(w, bytes) return } func WriteBytes(w io.Writer, bytes []byte) (int, error) { return w.Write(bytes) } func ReadByte(r io.Reader) (byte, error) { bytes, err := ReadBytes(r, 1) if err != nil { return 0x00, err } return bytes[0], nil } func ReadBytes(r io.Reader, n int) ([]byte, error) { bytes := make([]byte, n) m, err := r.Read(bytes) if err != nil { return bytes, err } if m != n { return bytes, fmt.Errorf("decode read bytes failed: expected %d got %d", m, n) } return bytes, nil } func WriteMarker(w io.Writer, m byte) error { return WriteByte(w, m) } func ReadMarker(r io.Reader) (byte, error) { return ReadByte(r) } func AssertMarker(r io.Reader, checkMarker bool, m byte) error { if checkMarker == false { return nil } marker, err := ReadMarker(r) if err != nil { return err } if marker != m { return fmt.Errorf("decode assert marker failed: expected %v got %v", m, marker) } return nil } ================================================ FILE: protocol/api/api.go ================================================ package api import ( "encoding/json" "fmt" "net" "net/http" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/protocol/rtmp" "github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay" jwtmiddleware "github.com/auth0/go-jwt-middleware" "github.com/dgrijalva/jwt-go" log "github.com/sirupsen/logrus" ) type Response struct { w http.ResponseWriter Status int `json:"status"` Data interface{} `json:"data"` } func (r *Response) SendJson() (int, error) { resp, _ := json.Marshal(r) r.w.Header().Set("Content-Type", "application/json") r.w.WriteHeader(r.Status) return r.w.Write(resp) } type Operation struct { Method string `json:"method"` URL string `json:"url"` Stop bool `json:"stop"` } type OperationChange struct { Method string `json:"method"` SourceURL string `json:"source_url"` TargetURL string `json:"target_url"` Stop bool `json:"stop"` } type ClientInfo struct { url string rtmpRemoteClient *rtmp.Client rtmpLocalClient *rtmp.Client } type Server struct { handler av.Handler session map[string]*rtmprelay.RtmpRelay rtmpAddr string } func NewServer(h av.Handler, rtmpAddr string) *Server { return &Server{ handler: h, session: make(map[string]*rtmprelay.RtmpRelay), rtmpAddr: rtmpAddr, } } func JWTMiddleware(next http.Handler) http.Handler { isJWT := len(configure.Config.GetString("jwt.secret")) > 0 if !isJWT { return next } log.Info("Using JWT middleware") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var algorithm jwt.SigningMethod if len(configure.Config.GetString("jwt.algorithm")) > 0 { algorithm = jwt.GetSigningMethod(configure.Config.GetString("jwt.algorithm")) } if algorithm == nil { algorithm = jwt.SigningMethodHS256 } jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ Extractor: jwtmiddleware.FromFirst(jwtmiddleware.FromAuthHeader, jwtmiddleware.FromParameter("jwt")), ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { return []byte(configure.Config.GetString("jwt.secret")), nil }, SigningMethod: algorithm, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) { res := &Response{ w: w, Status: 403, Data: err, } res.SendJson() }, }) jwtMiddleware.HandlerWithNext(w, r, next.ServeHTTP) }) } func (s *Server) Serve(l net.Listener) error { mux := http.NewServeMux() mux.Handle("/statics/", http.StripPrefix("/statics/", http.FileServer(http.Dir("statics")))) mux.HandleFunc("/control/push", func(w http.ResponseWriter, r *http.Request) { s.handlePush(w, r) }) mux.HandleFunc("/control/pull", func(w http.ResponseWriter, r *http.Request) { s.handlePull(w, r) }) mux.HandleFunc("/control/get", func(w http.ResponseWriter, r *http.Request) { s.handleGet(w, r) }) mux.HandleFunc("/control/reset", func(w http.ResponseWriter, r *http.Request) { s.handleReset(w, r) }) mux.HandleFunc("/control/delete", func(w http.ResponseWriter, r *http.Request) { s.handleDelete(w, r) }) mux.HandleFunc("/stat/livestat", func(w http.ResponseWriter, r *http.Request) { s.GetLiveStatics(w, r) }) http.Serve(l, JWTMiddleware(mux)) return nil } type stream struct { Key string `json:"key"` Url string `json:"url"` StreamId uint32 `json:"stream_id"` VideoTotalBytes uint64 `json:"video_total_bytes"` VideoSpeed uint64 `json:"video_speed"` AudioTotalBytes uint64 `json:"audio_total_bytes"` AudioSpeed uint64 `json:"audio_speed"` } type streams struct { Publishers []stream `json:"publishers"` Players []stream `json:"players"` } //http://127.0.0.1:8090/stat/livestat func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http.Request) { res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() room := "" if err := req.ParseForm(); err == nil { room = req.Form.Get("room") } rtmpStream := server.handler.(*rtmp.RtmpStream) if rtmpStream == nil { res.Status = 500 res.Data = "Get rtmp stream information error" return } msgs := new(streams) if room == "" { rtmpStream.GetStreams().Range(func(key, val interface{}) bool { if s, ok := val.(*rtmp.Stream); ok { if s.GetReader() != nil { switch s.GetReader().(type) { case *rtmp.VirReader: v := s.GetReader().(*rtmp.VirReader) msg := stream{key.(string), v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS, v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS} msgs.Publishers = append(msgs.Publishers, msg) } } } return true }) rtmpStream.GetStreams().Range(func(key, val interface{}) bool { ws := val.(*rtmp.Stream).GetWs() ws.Range(func(k, v interface{}) bool { if pw, ok := v.(*rtmp.PackWriterCloser); ok { if pw.GetWriter() != nil { switch pw.GetWriter().(type) { case *rtmp.VirWriter: v := pw.GetWriter().(*rtmp.VirWriter) msg := stream{key.(string), v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS, v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS} msgs.Players = append(msgs.Players, msg) } } } return true }) return true }) } else { // Warning: The room should be in the "live/stream" format! roomInfo, exists := (rtmpStream.GetStreams()).Load(room) if exists == false { res.Status = 404 res.Data = "room not found or inactive" return } if s, ok := roomInfo.(*rtmp.Stream); ok { if s.GetReader() != nil { switch s.GetReader().(type) { case *rtmp.VirReader: v := s.GetReader().(*rtmp.VirReader) msg := stream{room, v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS, v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS} msgs.Publishers = append(msgs.Publishers, msg) } } s.GetWs().Range(func(k, v interface{}) bool { if pw, ok := v.(*rtmp.PackWriterCloser); ok { if pw.GetWriter() != nil { switch pw.GetWriter().(type) { case *rtmp.VirWriter: v := pw.GetWriter().(*rtmp.VirWriter) msg := stream{room, v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS, v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS} msgs.Players = append(msgs.Players, msg) } } } return true }) } } //resp, _ := json.Marshal(msgs) res.Data = msgs } //http://127.0.0.1:8090/control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456 func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) { var retString string var err error res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() if req.ParseForm() != nil { res.Status = 400 res.Data = "url: /control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456" return } oper := req.Form.Get("oper") app := req.Form.Get("app") name := req.Form.Get("name") url := req.Form.Get("url") log.Debugf("control pull: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { res.Status = 400 res.Data = "control push parameter error, please check them." return } remoteurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name localurl := url keyString := "pull:" + app + "/" + name if oper == "stop" { pullRtmprelay, found := s.session[keyString] if !found { retString = fmt.Sprintf("session key[%s] not exist, please check it again.", keyString) res.Status = 400 res.Data = retString return } log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl) pullRtmprelay.Stop() delete(s.session, keyString) retString = fmt.Sprintf("

push url stop %s ok


", url) res.Status = 400 res.Data = retString log.Debugf("pull stop return %s", retString) } else { pullRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl) err = pullRtmprelay.Start() if err != nil { res.Status = 400 retString = fmt.Sprintf("push error=%v", err) } else { s.session[keyString] = pullRtmprelay retString = fmt.Sprintf("

pull url start %s ok


", url) } res.Data = retString log.Debugf("pull start return %s", retString) } } //http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456 func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) { var retString string var err error res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() if req.ParseForm() != nil { res.Data = "url: /control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456" return } oper := req.Form.Get("oper") app := req.Form.Get("app") name := req.Form.Get("name") url := req.Form.Get("url") log.Debugf("control push: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { res.Data = "control push parameter error, please check them." return } localurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name remoteurl := url keyString := "push:" + app + "/" + name if oper == "stop" { pushRtmprelay, found := s.session[keyString] if !found { retString = fmt.Sprintf("

session key[%s] not exist, please check it again.

", keyString) res.Data = retString return } log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl) pushRtmprelay.Stop() delete(s.session, keyString) retString = fmt.Sprintf("

push url stop %s ok


", url) res.Data = retString log.Debugf("push stop return %s", retString) } else { pushRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl) err = pushRtmprelay.Start() if err != nil { retString = fmt.Sprintf("push error=%v", err) } else { retString = fmt.Sprintf("

push url start %s ok


", url) s.session[keyString] = pushRtmprelay } res.Data = retString log.Debugf("push start return %s", retString) } } //http://127.0.0.1:8090/control/reset?room=ROOM_NAME func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) { res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() if err := r.ParseForm(); err != nil { res.Status = 400 res.Data = "url: /control/reset?room=" return } room := r.Form.Get("room") if len(room) == 0 { res.Status = 400 res.Data = "url: /control/reset?room=" return } msg, err := configure.RoomKeys.SetKey(room) if err != nil { msg = err.Error() res.Status = 400 } res.Data = msg } //http://127.0.0.1:8090/control/get?room=ROOM_NAME func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) { res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() if err := r.ParseForm(); err != nil { res.Status = 400 res.Data = "url: /control/get?room=" return } room := r.Form.Get("room") if len(room) == 0 { res.Status = 400 res.Data = "url: /control/get?room=" return } msg, err := configure.RoomKeys.GetKey(room) if err != nil { msg = err.Error() res.Status = 400 } res.Data = msg } //http://127.0.0.1:8090/control/delete?room=ROOM_NAME func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) { res := &Response{ w: w, Data: nil, Status: 200, } defer res.SendJson() if err := r.ParseForm(); err != nil { res.Status = 400 res.Data = "url: /control/delete?room=" return } room := r.Form.Get("room") if len(room) == 0 { res.Status = 400 res.Data = "url: /control/delete?room=" return } if configure.RoomKeys.DeleteChannel(room) { res.Data = "Ok" return } res.Status = 404 res.Data = "room not found" } ================================================ FILE: protocol/hls/align.go ================================================ package hls const ( syncms = 2 // ms ) type align struct { frameNum uint64 frameBase uint64 } func (a *align) align(dts *uint64, inc uint32) { aFrameDts := *dts estPts := a.frameBase + a.frameNum*uint64(inc) var dPts uint64 if estPts >= aFrameDts { dPts = estPts - aFrameDts } else { dPts = aFrameDts - estPts } if dPts <= uint64(syncms)*h264_default_hz { a.frameNum++ *dts = estPts return } a.frameNum = 1 a.frameBase = aFrameDts } ================================================ FILE: protocol/hls/audio_cache.go ================================================ package hls import "bytes" const ( cache_max_frames byte = 6 audio_cache_len int = 10 * 1024 ) type audioCache struct { soundFormat byte num byte offset int pts uint64 buf *bytes.Buffer } func newAudioCache() *audioCache { return &audioCache{ buf: bytes.NewBuffer(make([]byte, audio_cache_len)), } } func (a *audioCache) Cache(src []byte, pts uint64) bool { if a.num == 0 { a.offset = 0 a.pts = pts a.buf.Reset() } a.buf.Write(src) a.offset += len(src) a.num++ return false } func (a *audioCache) GetFrame() (int, uint64, []byte) { a.num = 0 return a.offset, a.pts, a.buf.Bytes() } func (a *audioCache) CacheNum() byte { return a.num } ================================================ FILE: protocol/hls/cache.go ================================================ package hls import ( "bytes" "container/list" "fmt" "sync" ) const ( maxTSCacheNum = 3 ) var ( ErrNoKey = fmt.Errorf("No key for cache") ) type TSCacheItem struct { id string num int lock sync.RWMutex ll *list.List lm map[string]TSItem } func NewTSCacheItem(id string) *TSCacheItem { return &TSCacheItem{ id: id, ll: list.New(), num: maxTSCacheNum, lm: make(map[string]TSItem), } } func (tcCacheItem *TSCacheItem) ID() string { return tcCacheItem.id } // TODO: found data race, fix it func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) { var seq int var getSeq bool var maxDuration int m3u8body := bytes.NewBuffer(nil) for e := tcCacheItem.ll.Front(); e != nil; e = e.Next() { key := e.Value.(string) v, ok := tcCacheItem.lm[key] if ok { if v.Duration > maxDuration { maxDuration = v.Duration } if !getSeq { getSeq = true seq = v.SeqNum } fmt.Fprintf(m3u8body, "#EXTINF:%.3f,\n%s\n", float64(v.Duration)/float64(1000), v.Name) } } w := bytes.NewBuffer(nil) fmt.Fprintf(w, "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:NO\n#EXT-X-TARGETDURATION:%d\n#EXT-X-MEDIA-SEQUENCE:%d\n\n", maxDuration/1000+1, seq) w.Write(m3u8body.Bytes()) return w.Bytes(), nil } func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) { if tcCacheItem.ll.Len() == tcCacheItem.num { e := tcCacheItem.ll.Front() tcCacheItem.ll.Remove(e) k := e.Value.(string) delete(tcCacheItem.lm, k) } tcCacheItem.lm[key] = item tcCacheItem.ll.PushBack(key) } func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) { item, ok := tcCacheItem.lm[key] if !ok { return item, ErrNoKey } return item, nil } ================================================ FILE: protocol/hls/hls.go ================================================ package hls import ( "fmt" "net" "net/http" "path" "strconv" "strings" "sync" "time" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/av" log "github.com/sirupsen/logrus" ) const ( duration = 3000 ) var ( ErrNoPublisher = fmt.Errorf("no publisher") ErrInvalidReq = fmt.Errorf("invalid req url path") ErrNoSupportVideoCodec = fmt.Errorf("no support video codec") ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec") ) var crossdomainxml = []byte(` `) type Server struct { listener net.Listener conns *sync.Map } func NewServer() *Server { ret := &Server{ conns: &sync.Map{}, } go ret.checkStop() return ret } func (server *Server) Serve(listener net.Listener) error { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { server.handle(w, r) }) server.listener = listener if configure.Config.GetBool("use_hls_https") { http.ServeTLS(listener, mux, "server.crt", "server.key") } else { http.Serve(listener, mux) } return nil } func (server *Server) GetWriter(info av.Info) av.WriteCloser { var s *Source v, ok := server.conns.Load(info.Key) if !ok { log.Debug("new hls source") s = NewSource(info) server.conns.Store(info.Key, s) } else { s = v.(*Source) } return s } func (server *Server) getConn(key string) *Source { v, ok := server.conns.Load(key) if !ok { return nil } return v.(*Source) } func (server *Server) checkStop() { for { <-time.After(5 * time.Second) server.conns.Range(func(key, val interface{}) bool { v := val.(*Source) if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") { log.Debug("check stop and remove: ", v.Info()) server.conns.Delete(key) } return true }) } } func (server *Server) handle(w http.ResponseWriter, r *http.Request) { if path.Base(r.URL.Path) == "crossdomain.xml" { w.Header().Set("Content-Type", "application/xml") w.Write(crossdomainxml) return } switch path.Ext(r.URL.Path) { case ".m3u8": key, _ := server.parseM3u8(r.URL.Path) conn := server.getConn(key) if conn == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } tsCache := conn.GetCacheInc() if tsCache == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } body, err := tsCache.GenM3U8PlayList() if err != nil { log.Debug("GenM3U8PlayList error: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Content-Type", "application/x-mpegURL") w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.Write(body) case ".ts": key, _ := server.parseTs(r.URL.Path) conn := server.getConn(key) if conn == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } tsCache := conn.GetCacheInc() item, err := tsCache.GetItem(r.URL.Path) if err != nil { log.Debug("GetItem error: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "video/mp2ts") w.Header().Set("Content-Length", strconv.Itoa(len(item.Data))) w.Write(item.Data) } } func (server *Server) parseM3u8(pathstr string) (key string, err error) { pathstr = strings.TrimLeft(pathstr, "/") key = strings.Split(pathstr, path.Ext(pathstr))[0] return } func (server *Server) parseTs(pathstr string) (key string, err error) { pathstr = strings.TrimLeft(pathstr, "/") paths := strings.SplitN(pathstr, "/", 3) if len(paths) != 3 { err = fmt.Errorf("invalid path=%s", pathstr) return } key = paths[0] + "/" + paths[1] return } ================================================ FILE: protocol/hls/item.go ================================================ package hls type TSItem struct { Name string SeqNum int Duration int Data []byte } func NewTSItem(name string, duration, seqNum int, b []byte) TSItem { var item TSItem item.Name = name item.SeqNum = seqNum item.Duration = duration item.Data = make([]byte, len(b)) copy(item.Data, b) return item } ================================================ FILE: protocol/hls/source.go ================================================ package hls import ( "bytes" "fmt" "github.com/gwuhaolin/livego/configure" "time" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/container/flv" "github.com/gwuhaolin/livego/container/ts" "github.com/gwuhaolin/livego/parser" log "github.com/sirupsen/logrus" ) const ( videoHZ = 90000 aacSampleLen = 1024 maxQueueNum = 512 h264_default_hz uint64 = 90 ) type Source struct { av.RWBaser seq int info av.Info bwriter *bytes.Buffer btswriter *bytes.Buffer demuxer *flv.Demuxer muxer *ts.Muxer pts, dts uint64 stat *status align *align cache *audioCache tsCache *TSCacheItem tsparser *parser.CodecParser closed bool packetQueue chan *av.Packet } func NewSource(info av.Info) *Source { info.Inter = true s := &Source{ info: info, align: &align{}, stat: newStatus(), RWBaser: av.NewRWBaser(time.Second * 10), cache: newAudioCache(), demuxer: flv.NewDemuxer(), muxer: ts.NewMuxer(), tsCache: NewTSCacheItem(info.Key), tsparser: parser.NewCodecParser(), bwriter: bytes.NewBuffer(make([]byte, 100*1024)), packetQueue: make(chan *av.Packet, maxQueueNum), } go func() { err := s.SendPacket() if err != nil { log.Debug("send packet error: ", err) s.closed = true } }() return s } func (source *Source) GetCacheInc() *TSCacheItem { return source.tsCache } func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) { log.Warningf("[%v] packet queue max!!!", info) for i := 0; i < maxQueueNum-84; i++ { tmpPkt, ok := <-pktQue // try to don't drop audio if ok && tmpPkt.IsAudio { if len(pktQue) > maxQueueNum-2 { <-pktQue } else { pktQue <- tmpPkt } } if ok && tmpPkt.IsVideo { videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) // dont't drop sps config and dont't drop key frame if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { pktQue <- tmpPkt } if len(pktQue) > maxQueueNum-10 { <-pktQue } } } log.Debug("packet queue len: ", len(pktQue)) } func (source *Source) Write(p *av.Packet) (err error) { err = nil if source.closed { err = fmt.Errorf("hls source closed") return } source.SetPreTime() defer func() { if e := recover(); e != nil { err = fmt.Errorf("hls source has already been closed:%v", e) } }() if len(source.packetQueue) >= maxQueueNum-24 { source.DropPacket(source.packetQueue, source.info) } else { if !source.closed { source.packetQueue <- p } } return } func (source *Source) SendPacket() error { defer func() { log.Debugf("[%v] hls sender stop", source.info) if r := recover(); r != nil { log.Warning("hls SendPacket panic: ", r) } }() log.Debugf("[%v] hls sender start", source.info) for { if source.closed { return fmt.Errorf("closed") } p, ok := <-source.packetQueue if ok { if p.IsMetadata { continue } err := source.demuxer.Demux(p) if err == flv.ErrAvcEndSEQ { log.Warning(err) continue } else { if err != nil { log.Warning(err) return err } } compositionTime, isSeq, err := source.parse(p) if err != nil { log.Warning(err) } if err != nil || isSeq { continue } if source.btswriter != nil { source.stat.update(p.IsVideo, p.TimeStamp) source.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime)) source.tsMux(p) } } else { return fmt.Errorf("closed") } } } func (source *Source) Info() (ret av.Info) { return source.info } func (source *Source) cleanup() { close(source.packetQueue) source.bwriter = nil source.btswriter = nil source.cache = nil source.tsCache = nil } func (source *Source) Close(err error) { log.Debug("hls source closed: ", source.info) if !source.closed && !configure.Config.GetBool("hls_keep_after_end") { source.cleanup() } source.closed = true } func (source *Source) cut() { newf := true if source.btswriter == nil { source.btswriter = bytes.NewBuffer(nil) } else if source.btswriter != nil && source.stat.durationMs() >= duration { source.flushAudio() source.seq++ filename := fmt.Sprintf("/%s/%d.ts", source.info.Key, time.Now().Unix()) item := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes()) source.tsCache.SetItem(filename, item) source.btswriter.Reset() source.stat.resetAndNew() } else { newf = false } if newf { source.btswriter.Write(source.muxer.PAT()) source.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true)) } } func (source *Source) parse(p *av.Packet) (int32, bool, error) { var compositionTime int32 var ah av.AudioPacketHeader var vh av.VideoPacketHeader if p.IsVideo { vh = p.Header.(av.VideoPacketHeader) if vh.CodecID() != av.VIDEO_H264 { return compositionTime, false, ErrNoSupportVideoCodec } compositionTime = vh.CompositionTime() if vh.IsKeyFrame() && vh.IsSeq() { return compositionTime, true, source.tsparser.Parse(p, source.bwriter) } } else { ah = p.Header.(av.AudioPacketHeader) if ah.SoundFormat() != av.SOUND_AAC { return compositionTime, false, ErrNoSupportAudioCodec } if ah.AACPacketType() == av.AAC_SEQHDR { return compositionTime, true, source.tsparser.Parse(p, source.bwriter) } } source.bwriter.Reset() if err := source.tsparser.Parse(p, source.bwriter); err != nil { return compositionTime, false, err } p.Data = source.bwriter.Bytes() if p.IsVideo && vh.IsKeyFrame() { source.cut() } return compositionTime, false, nil } func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) { source.dts = uint64(ts) * h264_default_hz if isVideo { source.pts = source.dts + uint64(compositionTs)*h264_default_hz } else { sampleRate, _ := source.tsparser.SampleRate() source.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate)) source.pts = source.dts } } func (source *Source) flushAudio() error { return source.muxAudio(1) } func (source *Source) muxAudio(limit byte) error { if source.cache.CacheNum() < limit { return nil } var p av.Packet _, pts, buf := source.cache.GetFrame() p.Data = buf p.TimeStamp = uint32(pts / h264_default_hz) return source.muxer.Mux(&p, source.btswriter) } func (source *Source) tsMux(p *av.Packet) error { if p.IsVideo { return source.muxer.Mux(p, source.btswriter) } else { source.cache.Cache(p.Data, source.pts) return source.muxAudio(cache_max_frames) } } ================================================ FILE: protocol/hls/status.go ================================================ package hls import "time" type status struct { hasVideo bool seqId int64 createdAt time.Time segBeginAt time.Time hasSetFirstTs bool firstTimestamp int64 lastTimestamp int64 } func newStatus() *status { return &status{ seqId: 0, hasSetFirstTs: false, segBeginAt: time.Now(), } } func (t *status) update(isVideo bool, timestamp uint32) { if isVideo { t.hasVideo = true } if !t.hasSetFirstTs { t.hasSetFirstTs = true t.firstTimestamp = int64(timestamp) } t.lastTimestamp = int64(timestamp) } func (t *status) resetAndNew() { t.seqId++ t.hasVideo = false t.createdAt = time.Now() t.hasSetFirstTs = false } func (t *status) durationMs() int64 { return t.lastTimestamp - t.firstTimestamp } ================================================ FILE: protocol/httpflv/server.go ================================================ package httpflv import ( "encoding/json" "net" "net/http" "strings" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/rtmp" log "github.com/sirupsen/logrus" ) type Server struct { handler av.Handler } type stream struct { Key string `json:"key"` Id string `json:"id"` } type streams struct { Publishers []stream `json:"publishers"` Players []stream `json:"players"` } func NewServer(h av.Handler) *Server { return &Server{ handler: h, } } func (server *Server) Serve(l net.Listener) error { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { server.handleConn(w, r) }) mux.HandleFunc("/streams", func(w http.ResponseWriter, r *http.Request) { server.getStream(w, r) }) if err := http.Serve(l, mux); err != nil { return err } return nil } // 获取发布和播放器的信息 func (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *streams { rtmpStream := server.handler.(*rtmp.RtmpStream) if rtmpStream == nil { return nil } msgs := new(streams) rtmpStream.GetStreams().Range(func(key, val interface{}) bool { if s, ok := val.(*rtmp.Stream); ok { if s.GetReader() != nil { msg := stream{key.(string), s.GetReader().Info().UID} msgs.Publishers = append(msgs.Publishers, msg) } } return true }) rtmpStream.GetStreams().Range(func(key, val interface{}) bool { ws := val.(*rtmp.Stream).GetWs() ws.Range(func(k, v interface{}) bool { if pw, ok := v.(*rtmp.PackWriterCloser); ok { if pw.GetWriter() != nil { msg := stream{key.(string), pw.GetWriter().Info().UID} msgs.Players = append(msgs.Players, msg) } } return true }) return true }) return msgs } func (server *Server) getStream(w http.ResponseWriter, r *http.Request) { msgs := server.getStreams(w, r) if msgs == nil { return } resp, _ := json.Marshal(msgs) w.Header().Set("Content-Type", "application/json") w.Write(resp) } func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { log.Error("http flv handleConn panic: ", r) } }() url := r.URL.String() u := r.URL.Path if pos := strings.LastIndex(u, "."); pos < 0 || u[pos:] != ".flv" { http.Error(w, "invalid path", http.StatusBadRequest) return } path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv") paths := strings.SplitN(path, "/", 2) log.Debug("url:", u, "path:", path, "paths:", paths) if len(paths) != 2 { http.Error(w, "invalid path", http.StatusBadRequest) return } // 判断视屏流是否发布,如果没有发布,直接返回404 msgs := server.getStreams(w, r) if msgs == nil || len(msgs.Publishers) == 0 { http.Error(w, "invalid path", http.StatusNotFound) return } else { include := false for _, item := range msgs.Publishers { if item.Key == path { include = true break } } if include == false { http.Error(w, "invalid path", http.StatusNotFound) return } } w.Header().Set("Access-Control-Allow-Origin", "*") writer := NewFLVWriter(paths[0], paths[1], url, w) server.handler.HandleWriter(writer) writer.Wait() } ================================================ FILE: protocol/httpflv/writer.go ================================================ package httpflv import ( "fmt" "net/http" "time" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/amf" "github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/uid" log "github.com/sirupsen/logrus" ) const ( headerLen = 11 maxQueueNum = 1024 ) type FLVWriter struct { Uid string av.RWBaser app, title, url string buf []byte closed bool closedChan chan struct{} ctx http.ResponseWriter packetQueue chan *av.Packet } func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter { ret := &FLVWriter{ Uid: uid.NewId(), app: app, title: title, url: url, ctx: ctx, RWBaser: av.NewRWBaser(time.Second * 10), closedChan: make(chan struct{}), buf: make([]byte, headerLen), packetQueue: make(chan *av.Packet, maxQueueNum), } if _, err := ret.ctx.Write([]byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}); err != nil { log.Errorf("Error on response writer") ret.closed = true } pio.PutI32BE(ret.buf[:4], 0) if _, err := ret.ctx.Write(ret.buf[:4]); err != nil { log.Errorf("Error on response writer") ret.closed = true } go func() { err := ret.SendPacket() if err != nil { log.Debug("SendPacket error: ", err) ret.closed = true } }() return ret } func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) { log.Warningf("[%v] packet queue max!!!", info) for i := 0; i < maxQueueNum-84; i++ { tmpPkt, ok := <-pktQue if ok && tmpPkt.IsVideo { videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) // dont't drop sps config and dont't drop key frame if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { log.Debug("insert keyframe to queue") pktQue <- tmpPkt } if len(pktQue) > maxQueueNum-10 { <-pktQue } // drop other packet <-pktQue } // try to don't drop audio if ok && tmpPkt.IsAudio { log.Debug("insert audio to queue") pktQue <- tmpPkt } } log.Debug("packet queue len: ", len(pktQue)) } func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) { err = nil if flvWriter.closed { err = fmt.Errorf("flvwrite source closed") return } defer func() { if e := recover(); e != nil { err = fmt.Errorf("FLVWriter has already been closed:%v", e) } }() if len(flvWriter.packetQueue) >= maxQueueNum-24 { flvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info()) } else { flvWriter.packetQueue <- p } return } func (flvWriter *FLVWriter) SendPacket() error { for { p, ok := <-flvWriter.packetQueue if ok { flvWriter.RWBaser.SetPreTime() h := flvWriter.buf[:headerLen] typeID := av.TAG_VIDEO if !p.IsVideo { if p.IsMetadata { var err error typeID = av.TAG_SCRIPTDATAAMF0 p.Data, err = amf.MetaDataReform(p.Data, amf.DEL) if err != nil { return err } } else { typeID = av.TAG_AUDIO } } dataLen := len(p.Data) timestamp := p.TimeStamp timestamp += flvWriter.BaseTimeStamp() flvWriter.RWBaser.RecTimeStamp(timestamp, uint32(typeID)) preDataLen := dataLen + headerLen timestampbase := timestamp & 0xffffff timestampExt := timestamp >> 24 & 0xff pio.PutU8(h[0:1], uint8(typeID)) pio.PutI24BE(h[1:4], int32(dataLen)) pio.PutI24BE(h[4:7], int32(timestampbase)) pio.PutU8(h[7:8], uint8(timestampExt)) if _, err := flvWriter.ctx.Write(h); err != nil { return err } if _, err := flvWriter.ctx.Write(p.Data); err != nil { return err } pio.PutI32BE(h[:4], int32(preDataLen)) if _, err := flvWriter.ctx.Write(h[:4]); err != nil { return err } } else { return fmt.Errorf("closed") } } } func (flvWriter *FLVWriter) Wait() { select { case <-flvWriter.closedChan: return } } func (flvWriter *FLVWriter) Close(error) { log.Debug("http flv closed") if !flvWriter.closed { close(flvWriter.packetQueue) close(flvWriter.closedChan) } flvWriter.closed = true } func (flvWriter *FLVWriter) Info() (ret av.Info) { ret.UID = flvWriter.Uid ret.URL = flvWriter.url ret.Key = flvWriter.app + "/" + flvWriter.title ret.Inter = true return } ================================================ FILE: protocol/rtmp/cache/cache.go ================================================ package cache import ( "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" ) type Cache struct { gop *GopCache videoSeq *SpecialCache audioSeq *SpecialCache metadata *SpecialCache } func NewCache() *Cache { return &Cache{ gop: NewGopCache(configure.Config.GetInt("gop_num")), videoSeq: NewSpecialCache(), audioSeq: NewSpecialCache(), metadata: NewSpecialCache(), } } func (cache *Cache) Write(p av.Packet) { if p.IsMetadata { cache.metadata.Write(&p) return } else { if !p.IsVideo { ah, ok := p.Header.(av.AudioPacketHeader) if ok { if ah.SoundFormat() == av.SOUND_AAC && ah.AACPacketType() == av.AAC_SEQHDR { cache.audioSeq.Write(&p) return } else { return } } } else { vh, ok := p.Header.(av.VideoPacketHeader) if ok { if vh.IsSeq() { cache.videoSeq.Write(&p) return } } else { return } } } cache.gop.Write(&p) } func (cache *Cache) Send(w av.WriteCloser) error { if err := cache.metadata.Send(w); err != nil { return err } if err := cache.videoSeq.Send(w); err != nil { return err } if err := cache.audioSeq.Send(w); err != nil { return err } if err := cache.gop.Send(w); err != nil { return err } return nil } ================================================ FILE: protocol/rtmp/cache/gop.go ================================================ package cache import ( "fmt" "github.com/gwuhaolin/livego/av" ) var ( maxGOPCap int = 1024 ErrGopTooBig = fmt.Errorf("gop to big") ) type array struct { index int packets []*av.Packet } func newArray() *array { ret := &array{ index: 0, packets: make([]*av.Packet, 0, maxGOPCap), } return ret } func (array *array) reset() { array.index = 0 array.packets = array.packets[:0] } func (array *array) write(packet *av.Packet) error { if array.index >= maxGOPCap { return ErrGopTooBig } array.packets = append(array.packets, packet) array.index++ return nil } func (array *array) send(w av.WriteCloser) error { var err error for i := 0; i < array.index; i++ { packet := array.packets[i] if err = w.Write(packet); err != nil { return err } } return err } type GopCache struct { start bool num int count int nextindex int gops []*array } func NewGopCache(num int) *GopCache { return &GopCache{ count: num, gops: make([]*array, num), } } func (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool) error { var ginc *array if startNew { ginc = gopCache.gops[gopCache.nextindex] if ginc == nil { ginc = newArray() gopCache.num++ gopCache.gops[gopCache.nextindex] = ginc } else { ginc.reset() } gopCache.nextindex = (gopCache.nextindex + 1) % gopCache.count } else { ginc = gopCache.gops[(gopCache.nextindex+1)%gopCache.count] } ginc.write(chunk) return nil } func (gopCache *GopCache) Write(p *av.Packet) { var ok bool if p.IsVideo { vh := p.Header.(av.VideoPacketHeader) if vh.IsKeyFrame() && !vh.IsSeq() { ok = true } } if ok || gopCache.start { gopCache.start = true gopCache.writeToArray(p, ok) } } func (gopCache *GopCache) sendTo(w av.WriteCloser) error { var err error pos := (gopCache.nextindex + 1) % gopCache.count for i := 0; i < gopCache.num; i++ { index := (pos - gopCache.num + 1) + i if index < 0 { index += gopCache.count } g := gopCache.gops[index] err = g.send(w) if err != nil { return err } } return nil } func (gopCache *GopCache) Send(w av.WriteCloser) error { return gopCache.sendTo(w) } ================================================ FILE: protocol/rtmp/cache/special.go ================================================ package cache import ( "bytes" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/amf" log "github.com/sirupsen/logrus" ) const ( SetDataFrame string = "@setDataFrame" OnMetaData string = "onMetaData" ) var setFrameFrame []byte func init() { b := bytes.NewBuffer(nil) encoder := &amf.Encoder{} if _, err := encoder.Encode(b, SetDataFrame, amf.AMF0); err != nil { log.Fatal(err) } setFrameFrame = b.Bytes() } type SpecialCache struct { full bool p *av.Packet } func NewSpecialCache() *SpecialCache { return &SpecialCache{} } func (specialCache *SpecialCache) Write(p *av.Packet) { specialCache.p = p specialCache.full = true } func (specialCache *SpecialCache) Send(w av.WriteCloser) error { if !specialCache.full { return nil } // demux in hls will change p.Data, only send a copy here newPacket := *specialCache.p return w.Write(&newPacket) } ================================================ FILE: protocol/rtmp/core/chunk_stream.go ================================================ package core import ( "encoding/binary" "fmt" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/utils/pool" ) type ChunkStream struct { Format uint32 CSID uint32 Timestamp uint32 Length uint32 TypeID uint32 StreamID uint32 timeDelta uint32 exted bool index uint32 remain uint32 got bool tmpFromat uint32 Data []byte } func (chunkStream *ChunkStream) full() bool { return chunkStream.got } func (chunkStream *ChunkStream) new(pool *pool.Pool) { chunkStream.got = false chunkStream.index = 0 chunkStream.remain = chunkStream.Length chunkStream.Data = pool.Get(int(chunkStream.Length)) } func (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error { //Chunk Basic Header h := chunkStream.Format << 6 switch { case chunkStream.CSID < 64: h |= chunkStream.CSID w.WriteUintBE(h, 1) case chunkStream.CSID-64 < 256: h |= 0 w.WriteUintBE(h, 1) w.WriteUintLE(chunkStream.CSID-64, 1) case chunkStream.CSID-64 < 65536: h |= 1 w.WriteUintBE(h, 1) w.WriteUintLE(chunkStream.CSID-64, 2) } //Chunk Message Header ts := chunkStream.Timestamp if chunkStream.Format == 3 { goto END } if chunkStream.Timestamp > 0xffffff { ts = 0xffffff } w.WriteUintBE(ts, 3) if chunkStream.Format == 2 { goto END } if chunkStream.Length > 0xffffff { return fmt.Errorf("length=%d", chunkStream.Length) } w.WriteUintBE(chunkStream.Length, 3) w.WriteUintBE(chunkStream.TypeID, 1) if chunkStream.Format == 1 { goto END } w.WriteUintLE(chunkStream.StreamID, 4) END: //Extended Timestamp if ts >= 0xffffff { w.WriteUintBE(chunkStream.Timestamp, 4) } return w.WriteError() } func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize int) error { if chunkStream.TypeID == av.TAG_AUDIO { chunkStream.CSID = 4 } else if chunkStream.TypeID == av.TAG_VIDEO || chunkStream.TypeID == av.TAG_SCRIPTDATAAMF0 || chunkStream.TypeID == av.TAG_SCRIPTDATAAMF3 { chunkStream.CSID = 6 } totalLen := uint32(0) numChunks := (chunkStream.Length / uint32(chunkSize)) for i := uint32(0); i <= numChunks; i++ { if totalLen == chunkStream.Length { break } if i == 0 { chunkStream.Format = uint32(0) } else { chunkStream.Format = uint32(3) } if err := chunkStream.writeHeader(w); err != nil { return err } inc := uint32(chunkSize) start := uint32(i) * uint32(chunkSize) if uint32(len(chunkStream.Data))-start <= inc { inc = uint32(len(chunkStream.Data)) - start } totalLen += inc end := start + inc buf := chunkStream.Data[start:end] if _, err := w.Write(buf); err != nil { return err } } return nil } func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error { if chunkStream.remain != 0 && chunkStream.tmpFromat != 3 { return fmt.Errorf("invalid remain = %d", chunkStream.remain) } switch chunkStream.CSID { case 0: id, _ := r.ReadUintLE(1) chunkStream.CSID = id + 64 case 1: id, _ := r.ReadUintLE(2) chunkStream.CSID = id + 64 } switch chunkStream.tmpFromat { case 0: chunkStream.Format = chunkStream.tmpFromat chunkStream.Timestamp, _ = r.ReadUintBE(3) chunkStream.Length, _ = r.ReadUintBE(3) chunkStream.TypeID, _ = r.ReadUintBE(1) chunkStream.StreamID, _ = r.ReadUintLE(4) if chunkStream.Timestamp == 0xffffff { chunkStream.Timestamp, _ = r.ReadUintBE(4) chunkStream.exted = true } else { chunkStream.exted = false } chunkStream.new(pool) case 1: chunkStream.Format = chunkStream.tmpFromat timeStamp, _ := r.ReadUintBE(3) chunkStream.Length, _ = r.ReadUintBE(3) chunkStream.TypeID, _ = r.ReadUintBE(1) if timeStamp == 0xffffff { timeStamp, _ = r.ReadUintBE(4) chunkStream.exted = true } else { chunkStream.exted = false } chunkStream.timeDelta = timeStamp chunkStream.Timestamp += timeStamp chunkStream.new(pool) case 2: chunkStream.Format = chunkStream.tmpFromat timeStamp, _ := r.ReadUintBE(3) if timeStamp == 0xffffff { timeStamp, _ = r.ReadUintBE(4) chunkStream.exted = true } else { chunkStream.exted = false } chunkStream.timeDelta = timeStamp chunkStream.Timestamp += timeStamp chunkStream.new(pool) case 3: if chunkStream.remain == 0 { switch chunkStream.Format { case 0: if chunkStream.exted { timestamp, _ := r.ReadUintBE(4) chunkStream.Timestamp = timestamp } case 1, 2: var timedet uint32 if chunkStream.exted { timedet, _ = r.ReadUintBE(4) } else { timedet = chunkStream.timeDelta } chunkStream.Timestamp += timedet } chunkStream.new(pool) } else { if chunkStream.exted { b, err := r.Peek(4) if err != nil { return err } tmpts := binary.BigEndian.Uint32(b) if tmpts == chunkStream.Timestamp { r.Discard(4) } } } default: return fmt.Errorf("invalid format=%d", chunkStream.Format) } size := int(chunkStream.remain) if size > int(chunkSize) { size = int(chunkSize) } buf := chunkStream.Data[chunkStream.index : chunkStream.index+uint32(size)] if _, err := r.Read(buf); err != nil { return err } chunkStream.index += uint32(size) chunkStream.remain -= uint32(size) if chunkStream.remain == 0 { chunkStream.got = true } return r.readError } ================================================ FILE: protocol/rtmp/core/chunk_stream_test.go ================================================ package core import ( "bytes" "testing" "github.com/gwuhaolin/livego/utils/pool" "github.com/stretchr/testify/assert" ) func TestChunkRead1(t *testing.T) { at := assert.New(t) data := []byte{ 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, } data1 := make([]byte, 128) data2 := make([]byte, 51) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data2...) rw := NewReadWriter(bytes.NewBuffer(data), 1024) chunkinc := &ChunkStream{} for { h, _ := rw.ReadUintBE(1) chunkinc.tmpFromat = h >> 6 chunkinc.CSID = h & 0x3f chunkinc.readChunk(rw, 128, pool.NewPool()) if chunkinc.remain == 0 { break } } at.Equal(int(chunkinc.Length), 307) at.Equal(int(chunkinc.TypeID), 9) at.Equal(int(chunkinc.StreamID), 1) at.Equal(len(chunkinc.Data), 307) at.Equal(int(chunkinc.remain), 0) data = []byte{ 0x06, 0xff, 0xff, 0xff, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, } data = append(data, data1...) data = append(data, 0xc6) data = append(data, []byte{0x00, 0x00, 0x00, 0x05}...) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data2...) rw = NewReadWriter(bytes.NewBuffer(data), 1024) chunkinc = &ChunkStream{} h, _ := rw.ReadUintBE(1) chunkinc.tmpFromat = h >> 6 chunkinc.CSID = h & 0x3f chunkinc.readChunk(rw, 128, pool.NewPool()) h, _ = rw.ReadUintBE(1) chunkinc.tmpFromat = h >> 6 chunkinc.CSID = h & 0x3f chunkinc.readChunk(rw, 128, pool.NewPool()) h, _ = rw.ReadUintBE(1) chunkinc.tmpFromat = h >> 6 chunkinc.CSID = h & 0x3f chunkinc.readChunk(rw, 128, pool.NewPool()) at.Equal(int(chunkinc.Length), 307) at.Equal(int(chunkinc.TypeID), 9) at.Equal(int(chunkinc.StreamID), 1) at.Equal(len(chunkinc.Data), 307) at.Equal(chunkinc.exted, true) at.Equal(int(chunkinc.Timestamp), 5) at.Equal(int(chunkinc.remain), 0) } func TestWriteChunk(t *testing.T) { at := assert.New(t) chunkinc := &ChunkStream{} chunkinc.Length = 307 chunkinc.TypeID = 9 chunkinc.CSID = 4 chunkinc.Timestamp = 40 chunkinc.Data = make([]byte, 307) bf := bytes.NewBuffer(nil) w := NewReadWriter(bf, 1024) err := chunkinc.writeChunk(w, 128) w.Flush() at.Equal(err, nil) at.Equal(len(bf.Bytes()), 321) } ================================================ FILE: protocol/rtmp/core/conn.go ================================================ package core import ( "encoding/binary" "net" "time" "github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/pool" ) const ( _ = iota idSetChunkSize idAbortMessage idAck idUserControlMessages idWindowAckSize idSetPeerBandwidth ) type Conn struct { net.Conn chunkSize uint32 remoteChunkSize uint32 windowAckSize uint32 remoteWindowAckSize uint32 received uint32 ackReceived uint32 rw *ReadWriter pool *pool.Pool chunks map[uint32]ChunkStream } func NewConn(c net.Conn, bufferSize int) *Conn { return &Conn{ Conn: c, chunkSize: 128, remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, pool: pool.NewPool(), rw: NewReadWriter(c, bufferSize), chunks: make(map[uint32]ChunkStream), } } func (conn *Conn) Read(c *ChunkStream) error { for { h, _ := conn.rw.ReadUintBE(1) // if err != nil { // log.Println("read from conn error: ", err) // return err // } format := h >> 6 csid := h & 0x3f cs, ok := conn.chunks[csid] if !ok { cs = ChunkStream{} conn.chunks[csid] = cs } cs.tmpFromat = format cs.CSID = csid err := cs.readChunk(conn.rw, conn.remoteChunkSize, conn.pool) if err != nil { return err } conn.chunks[csid] = cs if cs.full() { *c = cs break } } conn.handleControlMsg(c) conn.ack(c.Length) return nil } func (conn *Conn) Write(c *ChunkStream) error { if c.TypeID == idSetChunkSize { conn.chunkSize = binary.BigEndian.Uint32(c.Data) } return c.writeChunk(conn.rw, int(conn.chunkSize)) } func (conn *Conn) Flush() error { return conn.rw.Flush() } func (conn *Conn) Close() error { return conn.Conn.Close() } func (conn *Conn) RemoteAddr() net.Addr { return conn.Conn.RemoteAddr() } func (conn *Conn) LocalAddr() net.Addr { return conn.Conn.LocalAddr() } func (conn *Conn) SetDeadline(t time.Time) error { return conn.Conn.SetDeadline(t) } func (conn *Conn) NewAck(size uint32) ChunkStream { return initControlMsg(idAck, 4, size) } func (conn *Conn) NewSetChunkSize(size uint32) ChunkStream { return initControlMsg(idSetChunkSize, 4, size) } func (conn *Conn) NewWindowAckSize(size uint32) ChunkStream { return initControlMsg(idWindowAckSize, 4, size) } func (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream { ret := initControlMsg(idSetPeerBandwidth, 5, size) ret.Data[4] = 2 return ret } func (conn *Conn) handleControlMsg(c *ChunkStream) { if c.TypeID == idSetChunkSize { conn.remoteChunkSize = binary.BigEndian.Uint32(c.Data) } else if c.TypeID == idWindowAckSize { conn.remoteWindowAckSize = binary.BigEndian.Uint32(c.Data) } } func (conn *Conn) ack(size uint32) { conn.received += uint32(size) conn.ackReceived += uint32(size) if conn.received >= 0xf0000000 { conn.received = 0 } if conn.ackReceived >= conn.remoteWindowAckSize { cs := conn.NewAck(conn.ackReceived) cs.writeChunk(conn.rw, int(conn.chunkSize)) conn.ackReceived = 0 } } func initControlMsg(id, size, value uint32) ChunkStream { ret := ChunkStream{ Format: 0, CSID: 2, TypeID: id, StreamID: 0, Length: size, Data: make([]byte, size), } pio.PutU32BE(ret.Data[:size], value) return ret } const ( streamBegin uint32 = 0 streamEOF uint32 = 1 streamDry uint32 = 2 setBufferLen uint32 = 3 streamIsRecorded uint32 = 4 pingRequest uint32 = 6 pingResponse uint32 = 7 ) /* +------------------------------+------------------------- | Event Type ( 2- bytes ) | Event Data +------------------------------+------------------------- Pay load for the ‘User Control Message’. */ func (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream { var ret ChunkStream buflen += 2 ret = ChunkStream{ Format: 0, CSID: 2, TypeID: 4, StreamID: 1, Length: buflen, Data: make([]byte, buflen), } ret.Data[0] = byte(eventType >> 8 & 0xff) ret.Data[1] = byte(eventType & 0xff) return ret } func (conn *Conn) SetBegin() { ret := conn.userControlMsg(streamBegin, 4) for i := 0; i < 4; i++ { ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff) } conn.Write(&ret) } func (conn *Conn) SetRecorded() { ret := conn.userControlMsg(streamIsRecorded, 4) for i := 0; i < 4; i++ { ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff) } conn.Write(&ret) } ================================================ FILE: protocol/rtmp/core/conn_client.go ================================================ package core import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io" "math/rand" "net" neturl "net/url" "strings" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/protocol/amf" log "github.com/sirupsen/logrus" ) var ( respResult = "_result" respError = "_error" onStatus = "onStatus" publishStart = "NetStream.Publish.Start" playStart = "NetStream.Play.Start" connectSuccess = "NetConnection.Connect.Success" onBWDone = "onBWDone" ) var ( ErrFail = fmt.Errorf("response err") ) type ConnClient struct { done bool transID int url string tcurl string app string title string curcmdName string streamid uint32 isRTMPS bool conn *Conn encoder *amf.Encoder decoder *amf.Decoder bytesw *bytes.Buffer } func NewConnClient() *ConnClient { return &ConnClient{ transID: 1, bytesw: bytes.NewBuffer(nil), encoder: &amf.Encoder{}, decoder: &amf.Decoder{}, } } func (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version) (ret []interface{}, err error) { vs, err := connClient.decoder.DecodeBatch(r, ver) return vs, err } func (connClient *ConnClient) readRespMsg() error { var err error var rc ChunkStream for { if err = connClient.conn.Read(&rc); err != nil { return err } if err != nil && err != io.EOF { return err } switch rc.TypeID { case 20, 17: r := bytes.NewReader(rc.Data) vs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0) log.Debugf("readRespMsg: vs=%v", vs) for k, v := range vs { switch v.(type) { case string: switch connClient.curcmdName { case cmdConnect, cmdCreateStream: if v.(string) != respResult { return fmt.Errorf(v.(string)) } case cmdPublish: if v.(string) != onStatus { return ErrFail } } case float64: switch connClient.curcmdName { case cmdConnect, cmdCreateStream: id := int(v.(float64)) if k == 1 { if id != connClient.transID { return ErrFail } } else if k == 3 { connClient.streamid = uint32(id) } case cmdPublish: if int(v.(float64)) != 0 { return ErrFail } } case amf.Object: objmap := v.(amf.Object) switch connClient.curcmdName { case cmdConnect: code, ok := objmap["code"] if ok && code.(string) != connectSuccess { return ErrFail } case cmdPublish: code, ok := objmap["code"] if ok && code.(string) != publishStart { return ErrFail } } } } return nil } } } func (connClient *ConnClient) writeMsg(args ...interface{}) error { connClient.bytesw.Reset() for _, v := range args { if _, err := connClient.encoder.Encode(connClient.bytesw, v, amf.AMF0); err != nil { return err } } msg := connClient.bytesw.Bytes() c := ChunkStream{ Format: 0, CSID: 3, Timestamp: 0, TypeID: 20, StreamID: connClient.streamid, Length: uint32(len(msg)), Data: msg, } connClient.conn.Write(&c) return connClient.conn.Flush() } func (connClient *ConnClient) writeConnectMsg() error { event := make(amf.Object) event["app"] = connClient.app event["type"] = "nonprivate" event["flashVer"] = "FMS.3.1" event["tcUrl"] = connClient.tcurl connClient.curcmdName = cmdConnect log.Debugf("writeConnectMsg: connClient.transID=%d, event=%v", connClient.transID, event) if err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil { return err } return connClient.readRespMsg() } func (connClient *ConnClient) writeCreateStreamMsg() error { connClient.transID++ connClient.curcmdName = cmdCreateStream log.Debugf("writeCreateStreamMsg: connClient.transID=%d", connClient.transID) if err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil { return err } for { err := connClient.readRespMsg() if err == nil { return err } if err == ErrFail { log.Debugf("writeCreateStreamMsg readRespMsg err=%v", err) return err } } } func (connClient *ConnClient) writePublishMsg() error { connClient.transID++ connClient.curcmdName = cmdPublish if err := connClient.writeMsg(cmdPublish, connClient.transID, nil, connClient.title, publishLive); err != nil { return err } return connClient.readRespMsg() } func (connClient *ConnClient) writePlayMsg() error { connClient.transID++ connClient.curcmdName = cmdPlay log.Debugf("writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v", connClient.transID, cmdPlay, connClient.title) if err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil { return err } return connClient.readRespMsg() } func (connClient *ConnClient) Start(url string, method string) error { u, err := neturl.Parse(url) if err != nil { return err } connClient.url = url path := strings.TrimLeft(u.Path, "/") ps := strings.SplitN(path, "/", 2) if len(ps) != 2 { return fmt.Errorf("u path err: %s", path) } connClient.app = ps[0] connClient.title = ps[1] if u.RawQuery != "" { connClient.title += "?" + u.RawQuery } connClient.isRTMPS = strings.HasPrefix(url, "rtmps://") var port string if connClient.isRTMPS { connClient.tcurl = "rtmps://" + u.Host + "/" + connClient.app port = ":443" } else { connClient.tcurl = "rtmp://" + u.Host + "/" + connClient.app port = ":1935" } host := u.Host localIP := ":0" var remoteIP string if strings.Index(host, ":") != -1 { host, port, err = net.SplitHostPort(host) if err != nil { return err } port = ":" + port } ips, err := net.LookupIP(host) log.Debugf("ips: %v, host: %v", ips, host) if err != nil { log.Warning(err) return err } remoteIP = ips[rand.Intn(len(ips))].String() if strings.Index(remoteIP, ":") == -1 { remoteIP += port } local, err := net.ResolveTCPAddr("tcp", localIP) if err != nil { log.Warning(err) return err } log.Debug("remoteIP: ", remoteIP) remote, err := net.ResolveTCPAddr("tcp", remoteIP) if err != nil { log.Warning(err) return err } var conn net.Conn if connClient.isRTMPS { var config tls.Config if configure.Config.GetBool("enable_tls_verify") { roots, err := x509.SystemCertPool() if err != nil { log.Warning(err) return err } config.RootCAs = roots } else { config.InsecureSkipVerify = true } conn, err = tls.Dial("tcp", remoteIP, &config) if err != nil { log.Warning(err) return err } } else { conn, err = net.DialTCP("tcp", local, remote) if err != nil { log.Warning(err) return err } } log.Debug("connection:", "local:", conn.LocalAddr(), "remote:", conn.RemoteAddr()) connClient.conn = NewConn(conn, 4*1024) log.Debug("HandshakeClient....") if err := connClient.conn.HandshakeClient(); err != nil { return err } log.Debug("writeConnectMsg....") if err := connClient.writeConnectMsg(); err != nil { return err } log.Debug("writeCreateStreamMsg....") if err := connClient.writeCreateStreamMsg(); err != nil { log.Debug("writeCreateStreamMsg error", err) return err } log.Debug("method control:", method, av.PUBLISH, av.PLAY) if method == av.PUBLISH { if err := connClient.writePublishMsg(); err != nil { return err } } else if method == av.PLAY { if err := connClient.writePlayMsg(); err != nil { return err } } return nil } func (connClient *ConnClient) Write(c ChunkStream) error { if c.TypeID == av.TAG_SCRIPTDATAAMF0 || c.TypeID == av.TAG_SCRIPTDATAAMF3 { var err error if c.Data, err = amf.MetaDataReform(c.Data, amf.ADD); err != nil { return err } c.Length = uint32(len(c.Data)) } return connClient.conn.Write(&c) } func (connClient *ConnClient) Flush() error { return connClient.conn.Flush() } func (connClient *ConnClient) Read(c *ChunkStream) (err error) { return connClient.conn.Read(c) } func (connClient *ConnClient) GetInfo() (app string, name string, url string) { app = connClient.app name = connClient.title url = connClient.url return } func (connClient *ConnClient) GetStreamId() uint32 { return connClient.streamid } func (connClient *ConnClient) Close(err error) { connClient.conn.Close() } ================================================ FILE: protocol/rtmp/core/conn_server.go ================================================ package core import ( "bytes" "fmt" "io" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/amf" log "github.com/sirupsen/logrus" ) var ( publishLive = "live" publishRecord = "record" publishAppend = "append" ) var ( ErrReq = fmt.Errorf("req error") ) var ( cmdConnect = "connect" cmdFcpublish = "FCPublish" cmdReleaseStream = "releaseStream" cmdCreateStream = "createStream" cmdPublish = "publish" cmdFCUnpublish = "FCUnpublish" cmdDeleteStream = "deleteStream" cmdPlay = "play" ) type ConnectInfo struct { App string `amf:"app" json:"app"` Flashver string `amf:"flashVer" json:"flashVer"` SwfUrl string `amf:"swfUrl" json:"swfUrl"` TcUrl string `amf:"tcUrl" json:"tcUrl"` Fpad bool `amf:"fpad" json:"fpad"` AudioCodecs int `amf:"audioCodecs" json:"audioCodecs"` VideoCodecs int `amf:"videoCodecs" json:"videoCodecs"` VideoFunction int `amf:"videoFunction" json:"videoFunction"` PageUrl string `amf:"pageUrl" json:"pageUrl"` ObjectEncoding int `amf:"objectEncoding" json:"objectEncoding"` } type ConnectResp struct { FMSVer string `amf:"fmsVer"` Capabilities int `amf:"capabilities"` } type ConnectEvent struct { Level string `amf:"level"` Code string `amf:"code"` Description string `amf:"description"` ObjectEncoding int `amf:"objectEncoding"` } type PublishInfo struct { Name string Type string } type ConnServer struct { done bool streamID int isPublisher bool conn *Conn transactionID int ConnInfo ConnectInfo PublishInfo PublishInfo decoder *amf.Decoder encoder *amf.Encoder bytesw *bytes.Buffer } func NewConnServer(conn *Conn) *ConnServer { return &ConnServer{ conn: conn, streamID: 1, bytesw: bytes.NewBuffer(nil), decoder: &amf.Decoder{}, encoder: &amf.Encoder{}, } } func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ...interface{}) error { connServer.bytesw.Reset() for _, v := range args { if _, err := connServer.encoder.Encode(connServer.bytesw, v, amf.AMF0); err != nil { return err } } msg := connServer.bytesw.Bytes() c := ChunkStream{ Format: 0, CSID: csid, Timestamp: 0, TypeID: 20, StreamID: streamID, Length: uint32(len(msg)), Data: msg, } connServer.conn.Write(&c) return connServer.conn.Flush() } func (connServer *ConnServer) connect(vs []interface{}) error { for _, v := range vs { switch v.(type) { case string: case float64: id := int(v.(float64)) if id != 1 { return ErrReq } connServer.transactionID = id case amf.Object: obimap := v.(amf.Object) if app, ok := obimap["app"]; ok { connServer.ConnInfo.App = app.(string) } if flashVer, ok := obimap["flashVer"]; ok { connServer.ConnInfo.Flashver = flashVer.(string) } if tcurl, ok := obimap["tcUrl"]; ok { connServer.ConnInfo.TcUrl = tcurl.(string) } if encoding, ok := obimap["objectEncoding"]; ok { connServer.ConnInfo.ObjectEncoding = int(encoding.(float64)) } } } return nil } func (connServer *ConnServer) releaseStream(vs []interface{}) error { return nil } func (connServer *ConnServer) fcPublish(vs []interface{}) error { return nil } func (connServer *ConnServer) connectResp(cur *ChunkStream) error { c := connServer.conn.NewWindowAckSize(2500000) connServer.conn.Write(&c) c = connServer.conn.NewSetPeerBandwidth(2500000) connServer.conn.Write(&c) c = connServer.conn.NewSetChunkSize(uint32(1024)) connServer.conn.Write(&c) resp := make(amf.Object) resp["fmsVer"] = "FMS/3,0,1,123" resp["capabilities"] = 31 event := make(amf.Object) event["level"] = "status" event["code"] = "NetConnection.Connect.Success" event["description"] = "Connection succeeded." event["objectEncoding"] = connServer.ConnInfo.ObjectEncoding return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, resp, event) } func (connServer *ConnServer) createStream(vs []interface{}) error { for _, v := range vs { switch v.(type) { case string: case float64: connServer.transactionID = int(v.(float64)) case amf.Object: } } return nil } func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error { return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, nil, connServer.streamID) } func (connServer *ConnServer) publishOrPlay(vs []interface{}) error { for k, v := range vs { switch v.(type) { case string: if k == 2 { connServer.PublishInfo.Name = v.(string) } else if k == 3 { connServer.PublishInfo.Type = v.(string) } case float64: id := int(v.(float64)) connServer.transactionID = id case amf.Object: } } return nil } func (connServer *ConnServer) publishResp(cur *ChunkStream) error { event := make(amf.Object) event["level"] = "status" event["code"] = "NetStream.Publish.Start" event["description"] = "Start publishing." return connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event) } func (connServer *ConnServer) playResp(cur *ChunkStream) error { connServer.conn.SetRecorded() connServer.conn.SetBegin() event := make(amf.Object) event["level"] = "status" event["code"] = "NetStream.Play.Reset" event["description"] = "Playing and resetting stream." if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { return err } event["level"] = "status" event["code"] = "NetStream.Play.Start" event["description"] = "Started playing stream." if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { return err } event["level"] = "status" event["code"] = "NetStream.Data.Start" event["description"] = "Started playing stream." if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { return err } event["level"] = "status" event["code"] = "NetStream.Play.PublishNotify" event["description"] = "Started playing notify." if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { return err } return connServer.conn.Flush() } func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error { amfType := amf.AMF0 if c.TypeID == 17 { c.Data = c.Data[1:] } r := bytes.NewReader(c.Data) vs, err := connServer.decoder.DecodeBatch(r, amf.Version(amfType)) if err != nil && err != io.EOF { return err } // log.Debugf("rtmp req: %#v", vs) switch vs[0].(type) { case string: switch vs[0].(string) { case cmdConnect: if err = connServer.connect(vs[1:]); err != nil { return err } if err = connServer.connectResp(c); err != nil { return err } case cmdCreateStream: if err = connServer.createStream(vs[1:]); err != nil { return err } if err = connServer.createStreamResp(c); err != nil { return err } case cmdPublish: if err = connServer.publishOrPlay(vs[1:]); err != nil { return err } if err = connServer.publishResp(c); err != nil { return err } connServer.done = true connServer.isPublisher = true log.Debug("handle publish req done") case cmdPlay: if err = connServer.publishOrPlay(vs[1:]); err != nil { return err } if err = connServer.playResp(c); err != nil { return err } connServer.done = true connServer.isPublisher = false log.Debug("handle play req done") case cmdFcpublish: connServer.fcPublish(vs) case cmdReleaseStream: connServer.releaseStream(vs) case cmdFCUnpublish: case cmdDeleteStream: default: log.Debug("no support command=", vs[0].(string)) } } return nil } func (connServer *ConnServer) ReadMsg() error { var c ChunkStream for { if err := connServer.conn.Read(&c); err != nil { return err } switch c.TypeID { case 20, 17: if err := connServer.handleCmdMsg(&c); err != nil { return err } } if connServer.done { break } } return nil } func (connServer *ConnServer) IsPublisher() bool { return connServer.isPublisher } func (connServer *ConnServer) Write(c ChunkStream) error { if c.TypeID == av.TAG_SCRIPTDATAAMF0 || c.TypeID == av.TAG_SCRIPTDATAAMF3 { var err error if c.Data, err = amf.MetaDataReform(c.Data, amf.DEL); err != nil { return err } c.Length = uint32(len(c.Data)) } return connServer.conn.Write(&c) } func (connServer *ConnServer) Flush() error { return connServer.conn.Flush() } func (connServer *ConnServer) Read(c *ChunkStream) (err error) { return connServer.conn.Read(c) } func (connServer *ConnServer) GetInfo() (app string, name string, url string) { app = connServer.ConnInfo.App name = connServer.PublishInfo.Name url = connServer.ConnInfo.TcUrl + "/" + connServer.PublishInfo.Name return } func (connServer *ConnServer) Close(err error) { connServer.conn.Close() } ================================================ FILE: protocol/rtmp/core/conn_test.go ================================================ package core import ( "bytes" "io" "testing" "github.com/gwuhaolin/livego/utils/pool" "github.com/stretchr/testify/assert" ) func TestConnReadNormal(t *testing.T) { at := assert.New(t) data := []byte{ 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, } data1 := make([]byte, 128) data2 := make([]byte, 51) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data2...) conn := &Conn{ pool: pool.NewPool(), rw: NewReadWriter(bytes.NewBuffer(data), 1024), remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, chunks: make(map[uint32]ChunkStream), } var c ChunkStream err := conn.Read(&c) at.Equal(err, nil) at.Equal(int(c.CSID), 6) at.Equal(int(c.Length), 307) at.Equal(int(c.TypeID), 9) } //交叉读音视频数据 func TestConnCrossReading(t *testing.T) { at := assert.New(t) data1 := make([]byte, 128) data2 := make([]byte, 51) videoData := []byte{ 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, } audioData := []byte{ 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x08, 0x01, 0x00, 0x00, 0x00, } //video 1 videoData = append(videoData, data1...) //video 2 videoData = append(videoData, 0xc6) videoData = append(videoData, data1...) //audio 1 videoData = append(videoData, audioData...) videoData = append(videoData, data1...) //audio 2 videoData = append(videoData, 0xc4) videoData = append(videoData, data1...) //video 3 videoData = append(videoData, 0xc6) videoData = append(videoData, data2...) //audio 3 videoData = append(videoData, 0xc4) videoData = append(videoData, data2...) conn := &Conn{ pool: pool.NewPool(), rw: NewReadWriter(bytes.NewBuffer(videoData), 1024), remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, chunks: make(map[uint32]ChunkStream), } var c ChunkStream //video 1 err := conn.Read(&c) at.Equal(err, nil) at.Equal(int(c.TypeID), 9) at.Equal(len(c.Data), 307) //audio2 err = conn.Read(&c) at.Equal(err, nil) at.Equal(int(c.TypeID), 8) at.Equal(len(c.Data), 307) err = conn.Read(&c) at.Equal(err, io.EOF) } func TestSetChunksizeForWrite(t *testing.T) { at := assert.New(t) chunk := ChunkStream{ Format: 0, CSID: 2, Timestamp: 0, Length: 4, StreamID: 1, TypeID: idSetChunkSize, Data: []byte{0x00, 0x00, 0x00, 0x96}, } buf := bytes.NewBuffer(nil) rw := NewReadWriter(buf, 1024) conn := &Conn{ pool: pool.NewPool(), rw: rw, chunkSize: 128, remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, chunks: make(map[uint32]ChunkStream), } audio := ChunkStream{ Format: 0, CSID: 4, Timestamp: 40, Length: 133, StreamID: 1, TypeID: 0x8, } audio.Data = make([]byte, 133) audio.Data = audio.Data[:133] audio.Data[0] = 0xff audio.Data[128] = 0xff err := conn.Write(&audio) at.Equal(err, nil) conn.Flush() at.Equal(len(buf.Bytes()), 146) buf.Reset() err = conn.Write(&chunk) at.Equal(err, nil) conn.Flush() buf.Reset() err = conn.Write(&audio) at.Equal(err, nil) conn.Flush() at.Equal(len(buf.Bytes()), 145) } func TestSetChunksize(t *testing.T) { at := assert.New(t) data := []byte{ 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, } data1 := make([]byte, 128) data2 := make([]byte, 51) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data2...) rw := NewReadWriter(bytes.NewBuffer(data), 1024) conn := &Conn{ pool: pool.NewPool(), rw: rw, chunkSize: 128, remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, chunks: make(map[uint32]ChunkStream), } var c ChunkStream err := conn.Read(&c) at.Equal(err, nil) at.Equal(int(c.TypeID), 9) at.Equal(int(c.CSID), 6) at.Equal(int(c.StreamID), 1) at.Equal(len(c.Data), 307) //设置chunksize chunkBuf := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96} conn.rw = NewReadWriter(bytes.NewBuffer(chunkBuf), 1024) err = conn.Read(&c) at.Equal(err, nil) data = data[:12] data[7] = 0x8 data1 = make([]byte, 150) data2 = make([]byte, 7) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data1...) data = append(data, 0xc6) data = append(data, data2...) conn.rw = NewReadWriter(bytes.NewBuffer(data), 1024) err = conn.Read(&c) at.Equal(err, nil) at.Equal(len(c.Data), 307) err = conn.Read(&c) at.Equal(err, io.EOF) } func TestConnWrite(t *testing.T) { at := assert.New(t) wr := bytes.NewBuffer(nil) readWriter := NewReadWriter(wr, 128) conn := &Conn{ pool: pool.NewPool(), rw: readWriter, chunkSize: 128, remoteChunkSize: 128, windowAckSize: 2500000, remoteWindowAckSize: 2500000, chunks: make(map[uint32]ChunkStream), } c1 := ChunkStream{ Length: 3, TypeID: 8, CSID: 3, Timestamp: 40, Data: []byte{0x01, 0x02, 0x03}, } err := conn.Write(&c1) at.Equal(err, nil) conn.Flush() at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x28, 0x0, 0x0, 0x3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3}) //for type 1 wr.Reset() c1 = ChunkStream{ Length: 4, TypeID: 8, CSID: 3, Timestamp: 80, Data: []byte{0x01, 0x02, 0x03, 0x4}, } err = conn.Write(&c1) at.Equal(err, nil) conn.Flush() at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x50, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4}) //for type 2 wr.Reset() c1.Timestamp = 160 err = conn.Write(&c1) at.Equal(err, nil) conn.Flush() at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4}) } ================================================ FILE: protocol/rtmp/core/handshake.go ================================================ package core import ( "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" "fmt" "io" "time" "github.com/gwuhaolin/livego/utils/pio" ) var ( timeout = 5 * time.Second ) var ( hsClientFullKey = []byte{ 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, } hsServerFullKey = []byte{ 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, } hsClientPartialKey = hsClientFullKey[:30] hsServerPartialKey = hsServerFullKey[:36] ) func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) { h := hmac.New(sha256.New, key) if gap <= 0 { h.Write(src) } else { h.Write(src[:gap]) h.Write(src[gap+32:]) } return h.Sum(nil) } func hsCalcDigestPos(p []byte, base int) (pos int) { for i := 0; i < 4; i++ { pos += int(p[base+i]) } pos = (pos % 728) + base + 4 return } func hsFindDigest(p []byte, key []byte, base int) int { gap := hsCalcDigestPos(p, base) digest := hsMakeDigest(key, p, gap) if bytes.Compare(p[gap:gap+32], digest) != 0 { return -1 } return gap } func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []byte) { var pos int if pos = hsFindDigest(p, peerkey, 772); pos == -1 { if pos = hsFindDigest(p, peerkey, 8); pos == -1 { return } } ok = true digest = hsMakeDigest(key, p[pos:pos+32], -1) return } func hsCreate01(p []byte, time uint32, ver uint32, key []byte) { p[0] = 3 p1 := p[1:] rand.Read(p1[8:]) pio.PutU32BE(p1[0:4], time) pio.PutU32BE(p1[4:8], ver) gap := hsCalcDigestPos(p1, 8) digest := hsMakeDigest(key, p1, gap) copy(p1[gap:], digest) } func hsCreate2(p []byte, key []byte) { rand.Read(p) gap := len(p) - 32 digest := hsMakeDigest(key, p, gap) copy(p[gap:], digest) } func (conn *Conn) HandshakeClient() (err error) { var random [(1 + 1536*2) * 2]byte C0C1C2 := random[:1536*2+1] C0 := C0C1C2[:1] C0C1 := C0C1C2[:1536+1] C2 := C0C1C2[1536+1:] S0S1S2 := random[1536*2+1:] C0[0] = 3 // > C0C1 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = conn.rw.Write(C0C1); err != nil { return } conn.Conn.SetDeadline(time.Now().Add(timeout)) if err = conn.rw.Flush(); err != nil { return } // < S0S1S2 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = io.ReadFull(conn.rw, S0S1S2); err != nil { return } S1 := S0S1S2[1 : 1536+1] if ver := pio.U32BE(S1[4:8]); ver != 0 { C2 = S1 } else { C2 = S1 } // > C2 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = conn.rw.Write(C2); err != nil { return } conn.Conn.SetDeadline(time.Time{}) return } func (conn *Conn) HandshakeServer() (err error) { var random [(1 + 1536*2) * 2]byte C0C1C2 := random[:1536*2+1] C0 := C0C1C2[:1] C1 := C0C1C2[1 : 1536+1] C0C1 := C0C1C2[:1536+1] C2 := C0C1C2[1536+1:] S0S1S2 := random[1536*2+1:] S0 := S0S1S2[:1] S1 := S0S1S2[1 : 1536+1] S0S1 := S0S1S2[:1536+1] S2 := S0S1S2[1536+1:] // < C0C1 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = io.ReadFull(conn.rw, C0C1); err != nil { return } conn.Conn.SetDeadline(time.Now().Add(timeout)) if C0[0] != 3 { err = fmt.Errorf("rtmp: handshake version=%d invalid", C0[0]) return } S0[0] = 3 clitime := pio.U32BE(C1[0:4]) srvtime := clitime srvver := uint32(0x0d0e0a0d) cliver := pio.U32BE(C1[4:8]) if cliver != 0 { var ok bool var digest []byte if ok, digest = hsParse1(C1, hsClientPartialKey, hsServerFullKey); !ok { err = fmt.Errorf("rtmp: handshake server: C1 invalid") return } hsCreate01(S0S1, srvtime, srvver, hsServerPartialKey) hsCreate2(S2, digest) } else { copy(S1, C2) copy(S2, C1) } // > S0S1S2 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = conn.rw.Write(S0S1S2); err != nil { return } conn.Conn.SetDeadline(time.Now().Add(timeout)) if err = conn.rw.Flush(); err != nil { return } // < C2 conn.Conn.SetDeadline(time.Now().Add(timeout)) if _, err = io.ReadFull(conn.rw, C2); err != nil { return } conn.Conn.SetDeadline(time.Time{}) return } ================================================ FILE: protocol/rtmp/core/read_writer.go ================================================ package core import ( "bufio" "io" ) type ReadWriter struct { *bufio.ReadWriter readError error writeError error } func NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter { return &ReadWriter{ ReadWriter: bufio.NewReadWriter(bufio.NewReaderSize(rw, bufSize), bufio.NewWriterSize(rw, bufSize)), } } func (rw *ReadWriter) Read(p []byte) (int, error) { if rw.readError != nil { return 0, rw.readError } n, err := io.ReadAtLeast(rw.ReadWriter, p, len(p)) rw.readError = err return n, err } func (rw *ReadWriter) ReadError() error { return rw.readError } func (rw *ReadWriter) ReadUintBE(n int) (uint32, error) { if rw.readError != nil { return 0, rw.readError } ret := uint32(0) for i := 0; i < n; i++ { b, err := rw.ReadByte() if err != nil { rw.readError = err return 0, err } ret = ret<<8 + uint32(b) } return ret, nil } func (rw *ReadWriter) ReadUintLE(n int) (uint32, error) { if rw.readError != nil { return 0, rw.readError } ret := uint32(0) for i := 0; i < n; i++ { b, err := rw.ReadByte() if err != nil { rw.readError = err return 0, err } ret += uint32(b) << uint32(i*8) } return ret, nil } func (rw *ReadWriter) Flush() error { if rw.writeError != nil { return rw.writeError } if rw.ReadWriter.Writer.Buffered() == 0 { return nil } return rw.ReadWriter.Flush() } func (rw *ReadWriter) Write(p []byte) (int, error) { if rw.writeError != nil { return 0, rw.writeError } return rw.ReadWriter.Write(p) } func (rw *ReadWriter) WriteError() error { return rw.writeError } func (rw *ReadWriter) WriteUintBE(v uint32, n int) error { if rw.writeError != nil { return rw.writeError } for i := 0; i < n; i++ { b := byte(v>>uint32((n-i-1)<<3)) & 0xff if err := rw.WriteByte(b); err != nil { rw.writeError = err return err } } return nil } func (rw *ReadWriter) WriteUintLE(v uint32, n int) error { if rw.writeError != nil { return rw.writeError } for i := 0; i < n; i++ { b := byte(v) & 0xff if err := rw.WriteByte(b); err != nil { rw.writeError = err return err } v = v >> 8 } return nil } ================================================ FILE: protocol/rtmp/core/read_writer_test.go ================================================ package core import ( "bytes" "io" "testing" "github.com/stretchr/testify/assert" ) func TestReader(t *testing.T) { at := assert.New(t) buf := bytes.NewBufferString("abc") r := NewReadWriter(buf, 1024) b := make([]byte, 3) n, err := r.Read(b) at.Equal(err, nil) at.Equal(r.ReadError(), nil) at.Equal(n, 3) n, err = r.Read(b) at.Equal(err, io.EOF) at.Equal(r.ReadError(), io.EOF) buf.WriteString("123") n, err = r.Read(b) at.Equal(err, io.EOF) at.Equal(r.ReadError(), io.EOF) at.Equal(n, 0) } func TestReaderUintBE(t *testing.T) { at := assert.New(t) type Test struct { i int value uint32 bytes []byte } tests := []Test{ {1, 0x01, []byte{0x01}}, {2, 0x0102, []byte{0x01, 0x02}}, {3, 0x010203, []byte{0x01, 0x02, 0x03}}, {4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}}, } for _, test := range tests { buf := bytes.NewBuffer(test.bytes) r := NewReadWriter(buf, 1024) n, err := r.ReadUintBE(test.i) at.Equal(err, nil, "test %d", test.i) at.Equal(n, test.value, "test %d", test.i) } } func TestReaderUintLE(t *testing.T) { at := assert.New(t) type Test struct { i int value uint32 bytes []byte } tests := []Test{ {1, 0x01, []byte{0x01}}, {2, 0x0102, []byte{0x02, 0x01}}, {3, 0x010203, []byte{0x03, 0x02, 0x01}}, {4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}}, } for _, test := range tests { buf := bytes.NewBuffer(test.bytes) r := NewReadWriter(buf, 1024) n, err := r.ReadUintLE(test.i) at.Equal(err, nil, "test %d", test.i) at.Equal(n, test.value, "test %d", test.i) } } func TestWriter(t *testing.T) { at := assert.New(t) buf := bytes.NewBuffer(nil) w := NewReadWriter(buf, 1024) b := []byte{1, 2, 3} n, err := w.Write(b) at.Equal(err, nil) at.Equal(w.WriteError(), nil) at.Equal(n, 3) w.writeError = io.EOF n, err = w.Write(b) at.Equal(err, io.EOF) at.Equal(w.WriteError(), io.EOF) at.Equal(n, 0) } func TestWriteUintBE(t *testing.T) { at := assert.New(t) type Test struct { i int value uint32 bytes []byte } tests := []Test{ {1, 0x01, []byte{0x01}}, {2, 0x0102, []byte{0x01, 0x02}}, {3, 0x010203, []byte{0x01, 0x02, 0x03}}, {4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}}, } for _, test := range tests { buf := bytes.NewBuffer(nil) r := NewReadWriter(buf, 1024) err := r.WriteUintBE(test.value, test.i) at.Equal(err, nil, "test %d", test.i) err = r.Flush() at.Equal(err, nil, "test %d", test.i) at.Equal(buf.Bytes(), test.bytes, "test %d", test.i) } } func TestWriteUintLE(t *testing.T) { at := assert.New(t) type Test struct { i int value uint32 bytes []byte } tests := []Test{ {1, 0x01, []byte{0x01}}, {2, 0x0102, []byte{0x02, 0x01}}, {3, 0x010203, []byte{0x03, 0x02, 0x01}}, {4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}}, } for _, test := range tests { buf := bytes.NewBuffer(nil) r := NewReadWriter(buf, 1024) err := r.WriteUintLE(test.value, test.i) at.Equal(err, nil, "test %d", test.i) err = r.Flush() at.Equal(err, nil, "test %d", test.i) at.Equal(buf.Bytes(), test.bytes, "test %d", test.i) } } ================================================ FILE: protocol/rtmp/rtmp.go ================================================ package rtmp import ( "fmt" "net" "net/url" "reflect" "strings" "time" "github.com/gwuhaolin/livego/utils/uid" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/container/flv" "github.com/gwuhaolin/livego/protocol/rtmp/core" log "github.com/sirupsen/logrus" ) const ( maxQueueNum = 1024 SAVE_STATICS_INTERVAL = 5000 ) var ( readTimeout = configure.Config.GetInt("read_timeout") writeTimeout = configure.Config.GetInt("write_timeout") ) type Client struct { handler av.Handler getter av.GetWriter } func NewRtmpClient(h av.Handler, getter av.GetWriter) *Client { return &Client{ handler: h, getter: getter, } } func (c *Client) Dial(url string, method string) error { connClient := core.NewConnClient() if err := connClient.Start(url, method); err != nil { return err } if method == av.PUBLISH { writer := NewVirWriter(connClient) log.Debugf("client Dial call NewVirWriter url=%s, method=%s", url, method) c.handler.HandleWriter(writer) } else if method == av.PLAY { reader := NewVirReader(connClient) log.Debugf("client Dial call NewVirReader url=%s, method=%s", url, method) c.handler.HandleReader(reader) if c.getter != nil { writer := c.getter.GetWriter(reader.Info()) c.handler.HandleWriter(writer) } } return nil } func (c *Client) GetHandle() av.Handler { return c.handler } type Server struct { handler av.Handler getter av.GetWriter } func NewRtmpServer(h av.Handler, getter av.GetWriter) *Server { return &Server{ handler: h, getter: getter, } } func (s *Server) Serve(listener net.Listener) (err error) { defer func() { if r := recover(); r != nil { log.Error("rtmp serve panic: ", r) } }() for { var netconn net.Conn netconn, err = listener.Accept() if err != nil { return } conn := core.NewConn(netconn, 4*1024) log.Debug("new client, connect remote: ", conn.RemoteAddr().String(), "local:", conn.LocalAddr().String()) go s.handleConn(conn) } } func (s *Server) handleConn(conn *core.Conn) error { if err := conn.HandshakeServer(); err != nil { conn.Close() log.Error("handleConn HandshakeServer err: ", err) return err } connServer := core.NewConnServer(conn) if err := connServer.ReadMsg(); err != nil { conn.Close() log.Error("handleConn read msg err: ", err) return err } appname, name, _ := connServer.GetInfo() if ret := configure.CheckAppName(appname); !ret { err := fmt.Errorf("application name=%s is not configured", appname) conn.Close() log.Error("CheckAppName err: ", err) return err } log.Debugf("handleConn: IsPublisher=%v", connServer.IsPublisher()) if connServer.IsPublisher() { if configure.Config.GetBool("rtmp_noauth") { key, err := configure.RoomKeys.GetKey(name) if err != nil { err := fmt.Errorf("Cannot create key err=%s", err.Error()) conn.Close() log.Error("GetKey err: ", err) return err } name = key } channel, err := configure.RoomKeys.GetChannel(name) if err != nil { err := fmt.Errorf("invalid key err=%s", err.Error()) conn.Close() log.Error("CheckKey err: ", err) return err } connServer.PublishInfo.Name = channel if pushlist, ret := configure.GetStaticPushUrlList(appname); ret && (pushlist != nil) { log.Debugf("GetStaticPushUrlList: %v", pushlist) } reader := NewVirReader(connServer) s.handler.HandleReader(reader) log.Debugf("new publisher: %+v", reader.Info()) if s.getter != nil { writeType := reflect.TypeOf(s.getter) log.Debugf("handleConn:writeType=%v", writeType) writer := s.getter.GetWriter(reader.Info()) s.handler.HandleWriter(writer) } if configure.Config.GetBool("flv_archive") { flvWriter := new(flv.FlvDvr) s.handler.HandleWriter(flvWriter.GetWriter(reader.Info())) } } else { writer := NewVirWriter(connServer) log.Debugf("new player: %+v", writer.Info()) s.handler.HandleWriter(writer) } return nil } type GetInFo interface { GetInfo() (string, string, string) } type StreamReadWriteCloser interface { GetInFo Close(error) Write(core.ChunkStream) error Read(c *core.ChunkStream) error } type StaticsBW struct { StreamId uint32 VideoDatainBytes uint64 LastVideoDatainBytes uint64 VideoSpeedInBytesperMS uint64 AudioDatainBytes uint64 LastAudioDatainBytes uint64 AudioSpeedInBytesperMS uint64 LastTimestamp int64 } type VirWriter struct { Uid string closed bool av.RWBaser conn StreamReadWriteCloser packetQueue chan *av.Packet WriteBWInfo StaticsBW } func NewVirWriter(conn StreamReadWriteCloser) *VirWriter { ret := &VirWriter{ Uid: uid.NewId(), conn: conn, RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)), packetQueue: make(chan *av.Packet, maxQueueNum), WriteBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0}, } go ret.Check() go func() { err := ret.SendPacket() if err != nil { log.Warning(err) } }() return ret } func (v *VirWriter) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) { nowInMS := int64(time.Now().UnixNano() / 1e6) v.WriteBWInfo.StreamId = streamid if isVideoFlag { v.WriteBWInfo.VideoDatainBytes = v.WriteBWInfo.VideoDatainBytes + length } else { v.WriteBWInfo.AudioDatainBytes = v.WriteBWInfo.AudioDatainBytes + length } if v.WriteBWInfo.LastTimestamp == 0 { v.WriteBWInfo.LastTimestamp = nowInMS } else if (nowInMS - v.WriteBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL { diffTimestamp := (nowInMS - v.WriteBWInfo.LastTimestamp) / 1000 v.WriteBWInfo.VideoSpeedInBytesperMS = (v.WriteBWInfo.VideoDatainBytes - v.WriteBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000 v.WriteBWInfo.AudioSpeedInBytesperMS = (v.WriteBWInfo.AudioDatainBytes - v.WriteBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000 v.WriteBWInfo.LastVideoDatainBytes = v.WriteBWInfo.VideoDatainBytes v.WriteBWInfo.LastAudioDatainBytes = v.WriteBWInfo.AudioDatainBytes v.WriteBWInfo.LastTimestamp = nowInMS } } func (v *VirWriter) Check() { var c core.ChunkStream for { if err := v.conn.Read(&c); err != nil { v.Close(err) return } } } func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) { log.Warningf("[%v] packet queue max!!!", info) for i := 0; i < maxQueueNum-84; i++ { tmpPkt, ok := <-pktQue // try to don't drop audio if ok && tmpPkt.IsAudio { if len(pktQue) > maxQueueNum-2 { log.Debug("drop audio pkt") <-pktQue } else { pktQue <- tmpPkt } } if ok && tmpPkt.IsVideo { videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) // dont't drop sps config and dont't drop key frame if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { pktQue <- tmpPkt } if len(pktQue) > maxQueueNum-10 { log.Debug("drop video pkt") <-pktQue } } } log.Debug("packet queue len: ", len(pktQue)) } // func (v *VirWriter) Write(p *av.Packet) (err error) { err = nil if v.closed { err = fmt.Errorf("VirWriter closed") return } defer func() { if e := recover(); e != nil { err = fmt.Errorf("VirWriter has already been closed:%v", e) } }() if len(v.packetQueue) >= maxQueueNum-24 { v.DropPacket(v.packetQueue, v.Info()) } else { v.packetQueue <- p } return } func (v *VirWriter) SendPacket() error { Flush := reflect.ValueOf(v.conn).MethodByName("Flush") var cs core.ChunkStream for { p, ok := <-v.packetQueue if ok { cs.Data = p.Data cs.Length = uint32(len(p.Data)) cs.StreamID = p.StreamID cs.Timestamp = p.TimeStamp cs.Timestamp += v.BaseTimeStamp() if p.IsVideo { cs.TypeID = av.TAG_VIDEO } else { if p.IsMetadata { cs.TypeID = av.TAG_SCRIPTDATAAMF0 } else { cs.TypeID = av.TAG_AUDIO } } v.SaveStatics(p.StreamID, uint64(cs.Length), p.IsVideo) v.SetPreTime() v.RecTimeStamp(cs.Timestamp, cs.TypeID) err := v.conn.Write(cs) if err != nil { v.closed = true return err } Flush.Call(nil) } else { return fmt.Errorf("closed") } } } func (v *VirWriter) Info() (ret av.Info) { ret.UID = v.Uid _, _, URL := v.conn.GetInfo() ret.URL = URL _url, err := url.Parse(URL) if err != nil { log.Warning(err) } ret.Key = strings.TrimLeft(_url.Path, "/") ret.Inter = true return } func (v *VirWriter) Close(err error) { log.Warning("player ", v.Info(), "closed: "+err.Error()) if !v.closed { close(v.packetQueue) } v.closed = true v.conn.Close(err) } type VirReader struct { Uid string av.RWBaser demuxer *flv.Demuxer conn StreamReadWriteCloser ReadBWInfo StaticsBW } func NewVirReader(conn StreamReadWriteCloser) *VirReader { return &VirReader{ Uid: uid.NewId(), conn: conn, RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)), demuxer: flv.NewDemuxer(), ReadBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0}, } } func (v *VirReader) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) { nowInMS := int64(time.Now().UnixNano() / 1e6) v.ReadBWInfo.StreamId = streamid if isVideoFlag { v.ReadBWInfo.VideoDatainBytes = v.ReadBWInfo.VideoDatainBytes + length } else { v.ReadBWInfo.AudioDatainBytes = v.ReadBWInfo.AudioDatainBytes + length } if v.ReadBWInfo.LastTimestamp == 0 { v.ReadBWInfo.LastTimestamp = nowInMS } else if (nowInMS - v.ReadBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL { diffTimestamp := (nowInMS - v.ReadBWInfo.LastTimestamp) / 1000 //log.Printf("now=%d, last=%d, diff=%d", nowInMS, v.ReadBWInfo.LastTimestamp, diffTimestamp) v.ReadBWInfo.VideoSpeedInBytesperMS = (v.ReadBWInfo.VideoDatainBytes - v.ReadBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000 v.ReadBWInfo.AudioSpeedInBytesperMS = (v.ReadBWInfo.AudioDatainBytes - v.ReadBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000 v.ReadBWInfo.LastVideoDatainBytes = v.ReadBWInfo.VideoDatainBytes v.ReadBWInfo.LastAudioDatainBytes = v.ReadBWInfo.AudioDatainBytes v.ReadBWInfo.LastTimestamp = nowInMS } } func (v *VirReader) Read(p *av.Packet) (err error) { defer func() { if r := recover(); r != nil { log.Warning("rtmp read packet panic: ", r) } }() v.SetPreTime() var cs core.ChunkStream for { err = v.conn.Read(&cs) if err != nil { return err } if cs.TypeID == av.TAG_AUDIO || cs.TypeID == av.TAG_VIDEO || cs.TypeID == av.TAG_SCRIPTDATAAMF0 || cs.TypeID == av.TAG_SCRIPTDATAAMF3 { break } } p.IsAudio = cs.TypeID == av.TAG_AUDIO p.IsVideo = cs.TypeID == av.TAG_VIDEO p.IsMetadata = cs.TypeID == av.TAG_SCRIPTDATAAMF0 || cs.TypeID == av.TAG_SCRIPTDATAAMF3 p.StreamID = cs.StreamID p.Data = cs.Data p.TimeStamp = cs.Timestamp v.SaveStatics(p.StreamID, uint64(len(p.Data)), p.IsVideo) v.demuxer.DemuxH(p) return err } func (v *VirReader) Info() (ret av.Info) { ret.UID = v.Uid _, _, URL := v.conn.GetInfo() ret.URL = URL _url, err := url.Parse(URL) if err != nil { log.Warning(err) } ret.Key = strings.TrimLeft(_url.Path, "/") return } func (v *VirReader) Close(err error) { log.Debug("publisher ", v.Info(), "closed: "+err.Error()) v.conn.Close(err) } ================================================ FILE: protocol/rtmp/rtmprelay/rtmprelay.go ================================================ package rtmprelay import ( "bytes" "fmt" "io" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/amf" "github.com/gwuhaolin/livego/protocol/rtmp/core" log "github.com/sirupsen/logrus" ) var ( STOP_CTRL = "RTMPRELAY_STOP" ) type RtmpRelay struct { PlayUrl string PublishUrl string cs_chan chan core.ChunkStream sndctrl_chan chan string connectPlayClient *core.ConnClient connectPublishClient *core.ConnClient startflag bool } func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay { return &RtmpRelay{ PlayUrl: *playurl, PublishUrl: *publishurl, cs_chan: make(chan core.ChunkStream, 500), sndctrl_chan: make(chan string), connectPlayClient: nil, connectPublishClient: nil, startflag: false, } } func (self *RtmpRelay) rcvPlayChunkStream() { log.Debug("rcvPlayRtmpMediaPacket connectClient.Read...") for { var rc core.ChunkStream if self.startflag == false { self.connectPlayClient.Close(nil) log.Debugf("rcvPlayChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) break } err := self.connectPlayClient.Read(&rc) if err != nil && err == io.EOF { break } //log.Debugf("connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v", rc.TypeID, len(rc.Data), err) switch rc.TypeID { case 20, 17: r := bytes.NewReader(rc.Data) vs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0) log.Debugf("rcvPlayRtmpMediaPacket: vs=%v, err=%v", vs, err) case 18: log.Debug("rcvPlayRtmpMediaPacket: metadata....") self.cs_chan <- rc case 8, 9: self.cs_chan <- rc } } } func (self *RtmpRelay) sendPublishChunkStream() { for { select { case rc := <-self.cs_chan: //log.Debugf("sendPublishChunkStream: rc.TypeID=%v length=%d", rc.TypeID, len(rc.Data)) self.connectPublishClient.Write(rc) case ctrlcmd := <-self.sndctrl_chan: if ctrlcmd == STOP_CTRL { self.connectPublishClient.Close(nil) log.Debugf("sendPublishChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) return } } } } func (self *RtmpRelay) Start() error { if self.startflag { return fmt.Errorf("The rtmprelay already started, playurl=%s, publishurl=%s\n", self.PlayUrl, self.PublishUrl) } self.connectPlayClient = core.NewConnClient() self.connectPublishClient = core.NewConnClient() log.Debugf("play server addr:%v starting....", self.PlayUrl) err := self.connectPlayClient.Start(self.PlayUrl, av.PLAY) if err != nil { log.Debugf("connectPlayClient.Start url=%v error", self.PlayUrl) return err } log.Debugf("publish server addr:%v starting....", self.PublishUrl) err = self.connectPublishClient.Start(self.PublishUrl, av.PUBLISH) if err != nil { log.Debugf("connectPublishClient.Start url=%v error", self.PublishUrl) self.connectPlayClient.Close(nil) return err } self.startflag = true go self.rcvPlayChunkStream() go self.sendPublishChunkStream() return nil } func (self *RtmpRelay) Stop() { if !self.startflag { log.Debugf("The rtmprelay already stoped, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) return } self.startflag = false self.sndctrl_chan <- STOP_CTRL } ================================================ FILE: protocol/rtmp/rtmprelay/staticrelay.go ================================================ package rtmprelay import ( "fmt" "sync" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/protocol/rtmp/core" log "github.com/sirupsen/logrus" ) type StaticPush struct { RtmpUrl string packet_chan chan *av.Packet sndctrl_chan chan string connectClient *core.ConnClient startflag bool } var G_StaticPushMap = make(map[string](*StaticPush)) var g_MapLock = new(sync.RWMutex) var G_PushUrlList []string = nil var ( STATIC_RELAY_STOP_CTRL = "STATIC_RTMPRELAY_STOP" ) func GetStaticPushList(appname string) ([]string, error) { if G_PushUrlList == nil { // Do not unmarshel the config every time, lots of reflect works -gs pushurlList, ok := configure.GetStaticPushUrlList(appname) if !ok { G_PushUrlList = []string{} } else { G_PushUrlList = pushurlList } } if len(G_PushUrlList) == 0 { return nil, fmt.Errorf("no static push url") } return G_PushUrlList, nil } func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush { g_MapLock.RLock() staticpush, ok := G_StaticPushMap[rtmpurl] log.Debugf("GetAndCreateStaticPushObject: %s, return %v", rtmpurl, ok) if !ok { g_MapLock.RUnlock() newStaticpush := NewStaticPush(rtmpurl) g_MapLock.Lock() G_StaticPushMap[rtmpurl] = newStaticpush g_MapLock.Unlock() return newStaticpush } g_MapLock.RUnlock() return staticpush } func GetStaticPushObject(rtmpurl string) (*StaticPush, error) { g_MapLock.RLock() if staticpush, ok := G_StaticPushMap[rtmpurl]; ok { g_MapLock.RUnlock() return staticpush, nil } g_MapLock.RUnlock() return nil, fmt.Errorf("G_StaticPushMap[%s] not exist....", rtmpurl) } func ReleaseStaticPushObject(rtmpurl string) { g_MapLock.RLock() if _, ok := G_StaticPushMap[rtmpurl]; ok { g_MapLock.RUnlock() log.Debugf("ReleaseStaticPushObject %s ok", rtmpurl) g_MapLock.Lock() delete(G_StaticPushMap, rtmpurl) g_MapLock.Unlock() } else { g_MapLock.RUnlock() log.Debugf("ReleaseStaticPushObject: not find %s", rtmpurl) } } func NewStaticPush(rtmpurl string) *StaticPush { return &StaticPush{ RtmpUrl: rtmpurl, packet_chan: make(chan *av.Packet, 500), sndctrl_chan: make(chan string), connectClient: nil, startflag: false, } } func (self *StaticPush) Start() error { if self.startflag { return fmt.Errorf("StaticPush already start %s", self.RtmpUrl) } self.connectClient = core.NewConnClient() log.Debugf("static publish server addr:%v starting....", self.RtmpUrl) err := self.connectClient.Start(self.RtmpUrl, "publish") if err != nil { log.Debugf("connectClient.Start url=%v error", self.RtmpUrl) return err } log.Debugf("static publish server addr:%v started, streamid=%d", self.RtmpUrl, self.connectClient.GetStreamId()) go self.HandleAvPacket() self.startflag = true return nil } func (self *StaticPush) Stop() { if !self.startflag { return } log.Debugf("StaticPush Stop: %s", self.RtmpUrl) self.sndctrl_chan <- STATIC_RELAY_STOP_CTRL self.startflag = false } func (self *StaticPush) WriteAvPacket(packet *av.Packet) { if !self.startflag { return } self.packet_chan <- packet } func (self *StaticPush) sendPacket(p *av.Packet) { if !self.startflag { return } var cs core.ChunkStream cs.Data = p.Data cs.Length = uint32(len(p.Data)) cs.StreamID = self.connectClient.GetStreamId() cs.Timestamp = p.TimeStamp //cs.Timestamp += v.BaseTimeStamp() //log.Printf("Static sendPacket: rtmpurl=%s, length=%d, streamid=%d", // self.RtmpUrl, len(p.Data), cs.StreamID) if p.IsVideo { cs.TypeID = av.TAG_VIDEO } else { if p.IsMetadata { cs.TypeID = av.TAG_SCRIPTDATAAMF0 } else { cs.TypeID = av.TAG_AUDIO } } self.connectClient.Write(cs) } func (self *StaticPush) HandleAvPacket() { if !self.IsStart() { log.Debugf("static push %s not started", self.RtmpUrl) return } for { select { case packet := <-self.packet_chan: self.sendPacket(packet) case ctrlcmd := <-self.sndctrl_chan: if ctrlcmd == STATIC_RELAY_STOP_CTRL { self.connectClient.Close(nil) log.Debugf("Static HandleAvPacket close: publishurl=%s", self.RtmpUrl) return } } } } func (self *StaticPush) IsStart() bool { return self.startflag } ================================================ FILE: protocol/rtmp/stream.go ================================================ package rtmp import ( "fmt" "strings" "sync" "time" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/protocol/rtmp/cache" "github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay" log "github.com/sirupsen/logrus" ) var ( EmptyID = "" ) type RtmpStream struct { streams *sync.Map //key } func NewRtmpStream() *RtmpStream { ret := &RtmpStream{ streams: &sync.Map{}, } go ret.CheckAlive() return ret } func (rs *RtmpStream) HandleReader(r av.ReadCloser) { info := r.Info() log.Debugf("HandleReader: info[%v]", info) var stream *Stream i, ok := rs.streams.Load(info.Key) if stream, ok = i.(*Stream); ok { stream.TransStop() id := stream.ID() if id != EmptyID && id != info.UID { ns := NewStream() stream.Copy(ns) stream = ns rs.streams.Store(info.Key, ns) } } else { stream = NewStream() rs.streams.Store(info.Key, stream) stream.info = info } stream.AddReader(r) } func (rs *RtmpStream) HandleWriter(w av.WriteCloser) { info := w.Info() log.Debugf("HandleWriter: info[%v]", info) var s *Stream item, ok := rs.streams.Load(info.Key) if !ok { log.Debugf("HandleWriter: not found create new info[%v]", info) s = NewStream() rs.streams.Store(info.Key, s) s.info = info } else { s = item.(*Stream) s.AddWriter(w) } } func (rs *RtmpStream) GetStreams() *sync.Map { return rs.streams } func (rs *RtmpStream) CheckAlive() { for { <-time.After(5 * time.Second) rs.streams.Range(func(key, val interface{}) bool { v := val.(*Stream) if v.CheckAlive() == 0 { rs.streams.Delete(key) } return true }) } } type Stream struct { isStart bool cache *cache.Cache r av.ReadCloser ws *sync.Map info av.Info } type PackWriterCloser struct { init bool w av.WriteCloser } func (p *PackWriterCloser) GetWriter() av.WriteCloser { return p.w } func NewStream() *Stream { return &Stream{ cache: cache.NewCache(), ws: &sync.Map{}, } } func (s *Stream) ID() string { if s.r != nil { return s.r.Info().UID } return EmptyID } func (s *Stream) GetReader() av.ReadCloser { return s.r } func (s *Stream) GetWs() *sync.Map { return s.ws } func (s *Stream) Copy(dst *Stream) { dst.info = s.info s.ws.Range(func(key, val interface{}) bool { v := val.(*PackWriterCloser) s.ws.Delete(key) v.w.CalcBaseTimestamp() dst.AddWriter(v.w) return true }) } func (s *Stream) AddReader(r av.ReadCloser) { s.r = r go s.TransStart() } func (s *Stream) AddWriter(w av.WriteCloser) { info := w.Info() pw := &PackWriterCloser{w: w} s.ws.Store(info.UID, pw) } /*检测本application下是否配置static_push, 如果配置, 启动push远端的连接*/ func (s *Stream) StartStaticPush() { key := s.info.Key dscr := strings.Split(key, "/") if len(dscr) < 1 { return } index := strings.Index(key, "/") if index < 0 { return } streamname := key[index+1:] appname := dscr[0] log.Debugf("StartStaticPush: current streamname=%s, appname=%s", streamname, appname) pushurllist, err := rtmprelay.GetStaticPushList(appname) if err != nil || len(pushurllist) < 1 { log.Debugf("StartStaticPush: GetStaticPushList error=%v", err) return } for _, pushurl := range pushurllist { pushurl := pushurl + "/" + streamname log.Debugf("StartStaticPush: static pushurl=%s", pushurl) staticpushObj := rtmprelay.GetAndCreateStaticPushObject(pushurl) if staticpushObj != nil { if err := staticpushObj.Start(); err != nil { log.Debugf("StartStaticPush: staticpushObj.Start %s error=%v", pushurl, err) } else { log.Debugf("StartStaticPush: staticpushObj.Start %s ok", pushurl) } } else { log.Debugf("StartStaticPush GetStaticPushObject %s error", pushurl) } } } func (s *Stream) StopStaticPush() { key := s.info.Key log.Debugf("StopStaticPush......%s", key) dscr := strings.Split(key, "/") if len(dscr) < 1 { return } index := strings.Index(key, "/") if index < 0 { return } streamname := key[index+1:] appname := dscr[0] log.Debugf("StopStaticPush: current streamname=%s, appname=%s", streamname, appname) pushurllist, err := rtmprelay.GetStaticPushList(appname) if err != nil || len(pushurllist) < 1 { log.Debugf("StopStaticPush: GetStaticPushList error=%v", err) return } for _, pushurl := range pushurllist { pushurl := pushurl + "/" + streamname log.Debugf("StopStaticPush: static pushurl=%s", pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) if (staticpushObj != nil) && (err == nil) { staticpushObj.Stop() rtmprelay.ReleaseStaticPushObject(pushurl) log.Debugf("StopStaticPush: staticpushObj.Stop %s ", pushurl) } else { log.Debugf("StopStaticPush GetStaticPushObject %s error", pushurl) } } } func (s *Stream) IsSendStaticPush() bool { key := s.info.Key dscr := strings.Split(key, "/") if len(dscr) < 1 { return false } appname := dscr[0] //log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname) pushurllist, err := rtmprelay.GetStaticPushList(appname) if err != nil || len(pushurllist) < 1 { //log.Debugf("SendStaticPush: GetStaticPushList error=%v", err) return false } index := strings.Index(key, "/") if index < 0 { return false } streamname := key[index+1:] for _, pushurl := range pushurllist { pushurl := pushurl + "/" + streamname //log.Debugf("SendStaticPush: static pushurl=%s", pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) if (staticpushObj != nil) && (err == nil) { return true //staticpushObj.WriteAvPacket(&packet) //log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl) } else { log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl) } } return false } func (s *Stream) SendStaticPush(packet av.Packet) { key := s.info.Key dscr := strings.Split(key, "/") if len(dscr) < 1 { return } index := strings.Index(key, "/") if index < 0 { return } streamname := key[index+1:] appname := dscr[0] //log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname) pushurllist, err := rtmprelay.GetStaticPushList(appname) if err != nil || len(pushurllist) < 1 { //log.Debugf("SendStaticPush: GetStaticPushList error=%v", err) return } for _, pushurl := range pushurllist { pushurl := pushurl + "/" + streamname //log.Debugf("SendStaticPush: static pushurl=%s", pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) if (staticpushObj != nil) && (err == nil) { staticpushObj.WriteAvPacket(&packet) //log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl) } else { log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl) } } } func (s *Stream) TransStart() { s.isStart = true var p av.Packet log.Debugf("TransStart: %v", s.info) s.StartStaticPush() for { if !s.isStart { s.closeInter() return } err := s.r.Read(&p) if err != nil { s.closeInter() s.isStart = false return } if s.IsSendStaticPush() { s.SendStaticPush(p) } s.cache.Write(p) s.ws.Range(func(key, val interface{}) bool { v := val.(*PackWriterCloser) if !v.init { //log.Debugf("cache.send: %v", v.w.Info()) if err = s.cache.Send(v.w); err != nil { log.Debugf("[%s] send cache packet error: %v, remove", v.w.Info(), err) s.ws.Delete(key) return true } v.init = true } else { newPacket := p //writeType := reflect.TypeOf(v.w) //log.Debugf("w.Write: type=%v, %v", writeType, v.w.Info()) if err = v.w.Write(&newPacket); err != nil { log.Debugf("[%s] write packet error: %v, remove", v.w.Info(), err) s.ws.Delete(key) } } return true }) } } func (s *Stream) TransStop() { log.Debugf("TransStop: %s", s.info.Key) if s.isStart && s.r != nil { s.r.Close(fmt.Errorf("stop old")) } s.isStart = false } func (s *Stream) CheckAlive() (n int) { if s.r != nil && s.isStart { if s.r.Alive() { n++ } else { s.r.Close(fmt.Errorf("read timeout")) } } s.ws.Range(func(key, val interface{}) bool { v := val.(*PackWriterCloser) if v.w != nil { //Alive from RWBaser, check last frame now - timestamp, if > timeout then Remove it if !v.w.Alive() { log.Infof("write timeout remove") s.ws.Delete(key) v.w.Close(fmt.Errorf("write timeout")) return true } n++ } return true }) return } func (s *Stream) closeInter() { if s.r != nil { s.StopStaticPush() log.Debugf("[%v] publisher closed", s.r.Info()) } s.ws.Range(func(key, val interface{}) bool { v := val.(*PackWriterCloser) if v.w != nil { v.w.Close(fmt.Errorf("closed")) if v.w.Info().IsInterval() { s.ws.Delete(key) log.Debugf("[%v] player closed and remove\n", v.w.Info()) } } return true }) } ================================================ FILE: test.go ================================================ package main ================================================ FILE: utils/pio/pio.go ================================================ package pio var RecommendBufioSize = 1024 * 64 ================================================ FILE: utils/pio/reader.go ================================================ package pio func U8(b []byte) (i uint8) { return b[0] } func U16BE(b []byte) (i uint16) { i = uint16(b[0]) i <<= 8 i |= uint16(b[1]) return } func I16BE(b []byte) (i int16) { i = int16(b[0]) i <<= 8 i |= int16(b[1]) return } func I24BE(b []byte) (i int32) { i = int32(int8(b[0])) i <<= 8 i |= int32(b[1]) i <<= 8 i |= int32(b[2]) return } func U24BE(b []byte) (i uint32) { i = uint32(b[0]) i <<= 8 i |= uint32(b[1]) i <<= 8 i |= uint32(b[2]) return } func I32BE(b []byte) (i int32) { i = int32(int8(b[0])) i <<= 8 i |= int32(b[1]) i <<= 8 i |= int32(b[2]) i <<= 8 i |= int32(b[3]) return } func U32LE(b []byte) (i uint32) { i = uint32(b[3]) i <<= 8 i |= uint32(b[2]) i <<= 8 i |= uint32(b[1]) i <<= 8 i |= uint32(b[0]) return } func U32BE(b []byte) (i uint32) { i = uint32(b[0]) i <<= 8 i |= uint32(b[1]) i <<= 8 i |= uint32(b[2]) i <<= 8 i |= uint32(b[3]) return } func U40BE(b []byte) (i uint64) { i = uint64(b[0]) i <<= 8 i |= uint64(b[1]) i <<= 8 i |= uint64(b[2]) i <<= 8 i |= uint64(b[3]) i <<= 8 i |= uint64(b[4]) return } func U64BE(b []byte) (i uint64) { i = uint64(b[0]) i <<= 8 i |= uint64(b[1]) i <<= 8 i |= uint64(b[2]) i <<= 8 i |= uint64(b[3]) i <<= 8 i |= uint64(b[4]) i <<= 8 i |= uint64(b[5]) i <<= 8 i |= uint64(b[6]) i <<= 8 i |= uint64(b[7]) return } func I64BE(b []byte) (i int64) { i = int64(int8(b[0])) i <<= 8 i |= int64(b[1]) i <<= 8 i |= int64(b[2]) i <<= 8 i |= int64(b[3]) i <<= 8 i |= int64(b[4]) i <<= 8 i |= int64(b[5]) i <<= 8 i |= int64(b[6]) i <<= 8 i |= int64(b[7]) return } ================================================ FILE: utils/pio/writer.go ================================================ package pio func PutU8(b []byte, v uint8) { b[0] = v } func PutI16BE(b []byte, v int16) { b[0] = byte(v >> 8) b[1] = byte(v) } func PutU16BE(b []byte, v uint16) { b[0] = byte(v >> 8) b[1] = byte(v) } func PutI24BE(b []byte, v int32) { b[0] = byte(v >> 16) b[1] = byte(v >> 8) b[2] = byte(v) } func PutU24BE(b []byte, v uint32) { b[0] = byte(v >> 16) b[1] = byte(v >> 8) b[2] = byte(v) } func PutI32BE(b []byte, v int32) { b[0] = byte(v >> 24) b[1] = byte(v >> 16) b[2] = byte(v >> 8) b[3] = byte(v) } func PutU32BE(b []byte, v uint32) { b[0] = byte(v >> 24) b[1] = byte(v >> 16) b[2] = byte(v >> 8) b[3] = byte(v) } func PutU32LE(b []byte, v uint32) { b[3] = byte(v >> 24) b[2] = byte(v >> 16) b[1] = byte(v >> 8) b[0] = byte(v) } func PutU40BE(b []byte, v uint64) { b[0] = byte(v >> 32) b[1] = byte(v >> 24) b[2] = byte(v >> 16) b[3] = byte(v >> 8) b[4] = byte(v) } func PutU48BE(b []byte, v uint64) { b[0] = byte(v >> 40) b[1] = byte(v >> 32) b[2] = byte(v >> 24) b[3] = byte(v >> 16) b[4] = byte(v >> 8) b[5] = byte(v) } func PutU64BE(b []byte, v uint64) { b[0] = byte(v >> 56) b[1] = byte(v >> 48) b[2] = byte(v >> 40) b[3] = byte(v >> 32) b[4] = byte(v >> 24) b[5] = byte(v >> 16) b[6] = byte(v >> 8) b[7] = byte(v) } func PutI64BE(b []byte, v int64) { b[0] = byte(v >> 56) b[1] = byte(v >> 48) b[2] = byte(v >> 40) b[3] = byte(v >> 32) b[4] = byte(v >> 24) b[5] = byte(v >> 16) b[6] = byte(v >> 8) b[7] = byte(v) } ================================================ FILE: utils/pool/pool.go ================================================ package pool type Pool struct { pos int buf []byte } const maxpoolsize = 500 * 1024 func (pool *Pool) Get(size int) []byte { if maxpoolsize-pool.pos < size { pool.pos = 0 pool.buf = make([]byte, maxpoolsize) } b := pool.buf[pool.pos : pool.pos+size] pool.pos += size return b } func NewPool() *Pool { return &Pool{ buf: make([]byte, maxpoolsize), } } ================================================ FILE: utils/queue/queue.go ================================================ package queue import ( "sync" "github.com/gwuhaolin/livego/av" ) // Queue is a basic FIFO queue for Messages. type Queue struct { maxSize int list []*av.Packet mutex sync.Mutex } // NewQueue returns a new Queue. If maxSize is greater than zero the queue will // not grow more than the defined size. func NewQueue(maxSize int) *Queue { return &Queue{ maxSize: maxSize, } } // Push adds a message to the queue. func (q *Queue) Push(msg *av.Packet) { q.mutex.Lock() defer q.mutex.Unlock() if len(q.list) == q.maxSize { q.pop() } q.list = append(q.list, msg) } // Pop removes and returns a message from the queue in first to last order. func (q *Queue) Pop() *av.Packet { q.mutex.Lock() defer q.mutex.Unlock() if len(q.list) == 0 { return nil } return q.pop() } func (q *Queue) pop() *av.Packet { x := len(q.list) - 1 msg := q.list[x] q.list = q.list[:x] return msg } // Len returns the length of the queue. func (q *Queue) Len() int { q.mutex.Lock() defer q.mutex.Unlock() return len(q.list) } // All returns and removes all messages from the queue. func (q *Queue) All() []*av.Packet { q.mutex.Lock() defer q.mutex.Unlock() cache := q.list q.list = nil return cache } ================================================ FILE: utils/uid/rand.go ================================================ package uid import "math/rand" var letterRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) } ================================================ FILE: utils/uid/uuid.go ================================================ package uid import ( "encoding/base64" "github.com/satori/go.uuid" ) func NewId() string { id := uuid.NewV4() b64 := base64.URLEncoding.EncodeToString(id.Bytes()[:12]) return b64 }