├── .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 ├── logo.png ├── 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 /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | schedule: 20 | - cron: '30 23 * * 3' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: go 47 | build-mode: autobuild 48 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 49 | # Use `c-cpp` to analyze code written in C, C++ or both 50 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 51 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 52 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 53 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 54 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 55 | # 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 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | # Initializes the CodeQL tools for scanning. 61 | - name: Initialize CodeQL 62 | uses: github/codeql-action/init@v3 63 | with: 64 | languages: ${{ matrix.language }} 65 | build-mode: ${{ matrix.build-mode }} 66 | # If you wish to specify custom queries, you can do so here or in a config file. 67 | # By default, queries listed here will override any specified in a config file. 68 | # Prefix the list here with "+" to use these queries and those in the config file. 69 | 70 | # 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 71 | # queries: security-extended,security-and-quality 72 | 73 | # If the analyze step fails for one of the languages you are analyzing with 74 | # "We were unable to automatically build your code", modify the matrix above 75 | # to set the build mode to "manual" for that language. Then modify this step 76 | # to build your code. 77 | # ℹ️ Command-line programs to run using the OS shell. 78 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 79 | - if: matrix.build-mode == 'manual' 80 | shell: bash 81 | run: | 82 | echo 'If you are using a "manual" build mode for one or more of the' \ 83 | 'languages you are analyzing, replace this with the commands to build' \ 84 | 'your code, for example:' 85 | echo ' make bootstrap' 86 | echo ' make release' 87 | exit 1 88 | 89 | - name: Perform CodeQL Analysis 90 | uses: github/codeql-action/analyze@v3 91 | with: 92 | category: "/language:${{matrix.language}}" 93 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | goreleaser: 7 | runs-on: ubuntu-latest 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | steps: 11 | - name: Set up Go 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version: '>=1.18.0' 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v4 17 | - name: Get dependencies 18 | run: go get 19 | - name: Go release 20 | uses: goreleaser/goreleaser-action@v1 21 | - name: Docker release 22 | uses: elgohr/Publish-Docker-Github-Action@master 23 | with: 24 | name: gwuhaolin/livego 25 | username: ${{ secrets.DOCKER_USERNAME }} 26 | password: ${{ secrets.DOCKER_PASSWORD }} 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Build 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, windows-latest, macOS-latest] 10 | 11 | steps: 12 | - name: Set up Go 13 | uses: actions/setup-go@v5 14 | with: 15 | go-version: '>=1.18.0' 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v4 18 | - name: Get dependencies 19 | run: go get 20 | - name: Test 21 | run: go test ./... 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | dist 4 | .vscode 5 | tmp 6 | vendor 7 | livego 8 | livego.exe 9 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | builds: 5 | - binary: livego 6 | id: livego 7 | main: ./main.go 8 | goos: 9 | - windows 10 | - darwin 11 | - linux 12 | - freebsd 13 | goarch: 14 | - amd64 15 | - 386 16 | - arm -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ### Added 10 | - JSON Web Token support. 11 | ``` json 12 | // livego.json 13 | { 14 | "jwt": { 15 | "secret": "testing", 16 | "algorithm": "HS256" 17 | }, 18 | "server": [ 19 | { 20 | "appname": "live", 21 | "live": true, 22 | "hls": true 23 | } 24 | ] 25 | } 26 | ``` 27 | - Use redis for store room keys 28 | ``` json 29 | // livego.json 30 | { 31 | "redis_addr": "localhost:6379", 32 | "server": [ 33 | { 34 | "appname": "live", 35 | "live": true, 36 | "hls": true 37 | } 38 | ] 39 | } 40 | ``` 41 | - Makefile 42 | 43 | ### Changed 44 | - Show `players`. 45 | - Show `stream_id`. 46 | - Deleted keys saved in physical file, now the keys are in cached using `go-cache` by default. 47 | - Using `logrus` like log system. 48 | - Using method `.Get(queryParamName)` to get an url query param. 49 | - Replaced `errors.New(...)` to `fmt.Errorf(...)`. 50 | - Replaced types string on config params `liveon` and `hlson` to booleans `live: true/false` and `hls: true/false` 51 | - Using viper for config, allow use file, cloud providers, environment vars or flags. 52 | - Using yaml config by default. 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest as builder 2 | WORKDIR /app 3 | ENV GOPROXY https://goproxy.io 4 | COPY go.mod go.sum ./ 5 | RUN go mod download 6 | COPY . . 7 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego . 8 | 9 | FROM alpine:latest 10 | RUN mkdir -p /app/config 11 | WORKDIR /app 12 | ENV RTMP_PORT 1935 13 | ENV HTTP_FLV_PORT 7001 14 | ENV HLS_PORT 7002 15 | ENV HTTP_OPERATION_PORT 8090 16 | COPY --from=builder /app/livego . 17 | EXPOSE ${RTMP_PORT} 18 | EXPOSE ${HTTP_FLV_PORT} 19 | EXPOSE ${HLS_PORT} 20 | EXPOSE ${HTTP_OPERATION_PORT} 21 | ENTRYPOINT ["./livego"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 吴浩麟 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOCMD ?= go 2 | GOBUILD = $(GOCMD) build 3 | GOCLEAN = $(GOCMD) clean 4 | GOTEST = $(GOCMD) test 5 | GOGET = $(GOCMD) get 6 | BINARY_NAME = livego 7 | BINARY_UNIX = $(BINARY_NAME)_unix 8 | 9 | DOCKER_ACC ?= gwuhaolin 10 | DOCKER_REPO ?= livego 11 | 12 | TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null) 13 | 14 | default: all 15 | 16 | all: test build dockerize 17 | build: 18 | $(GOBUILD) -o $(BINARY_NAME) -v -ldflags="-X main.VERSION=$(TAG)" 19 | 20 | test: 21 | $(GOTEST) -v ./... 22 | 23 | clean: 24 | $(GOCLEAN) 25 | rm -f $(BINARY_NAME) 26 | rm -f $(BINARY_UNIX) 27 | 28 | run: build 29 | ./$(BINARY_NAME) 30 | 31 | build-linux: 32 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v 33 | 34 | dockerize: 35 | docker build -t $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) . 36 | docker push $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [中文](./README_cn.md) 6 | 7 | [![Test](https://github.com/gwuhaolin/livego/actions/workflows/test.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/test.yml) 8 | [![Release](https://github.com/gwuhaolin/livego/actions/workflows/release.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/release.yml) 9 | [![CodeQL Advanced](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml) 10 | 11 | Simple and efficient live broadcast server: 12 | - Very simple to install and use; 13 | - Pure Golang, high performance, and cross-platform; 14 | - Supports commonly used transmission protocols, file formats, and encoding formats; 15 | 16 | #### Supported transport protocols 17 | - RTMP 18 | - AMF 19 | - HLS 20 | - HTTP-FLV 21 | 22 | #### Supported container formats 23 | - FLV 24 | - TS 25 | 26 | #### Supported encoding formats 27 | - H264 28 | - AAC 29 | - MP3 30 | 31 | ## Installation 32 | After directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line. 33 | 34 | #### Boot from Docker 35 | Run `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start 36 | 37 | #### Compile from source 38 | 1. Download the source code `git clone https://github.com/gwuhaolin/livego.git` 39 | 2. Go to the livego directory and execute `go build` or `make build` 40 | 41 | ## Use 42 | 1. Start the service: execute the livego binary file or `make run` to start the livego service; 43 | 2. Get a channelkey(used for push the video stream) from `http://localhost:8090/control/get?room=movie` and copy data like your channelkey. 44 | 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)); 45 | 4. Downstream playback: The following three playback protocols are supported, and the playback address is as follows: 46 | - `RTMP`:`rtmp://localhost:1935/{appname}/movie` 47 | - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv` 48 | - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8` 49 | 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) 50 | 51 | all options: 52 | ```bash 53 | ./livego -h 54 | Usage of ./livego: 55 | --api_addr string HTTP manage interface server listen address (default ":8090") 56 | --config_file string configure filename (default "livego.yaml") 57 | --flv_dir string output flv file at flvDir/APP/KEY_TIME.flv (default "tmp") 58 | --gop_num int gop num (default 1) 59 | --hls_addr string HLS server listen address (default ":7002") 60 | --hls_keep_after_end Maintains the HLS after the stream ends 61 | --httpflv_addr string HTTP-FLV server listen address (default ":7001") 62 | --level string Log level (default "info") 63 | --read_timeout int read time out (default 10) 64 | --rtmp_addr string RTMP server listen address 65 | ``` 66 | 67 | ### [Use with flv.js](https://github.com/gwuhaolin/blog/issues/3) 68 | 69 | Interested in Golang? Please see [Golang Chinese Learning Materials Summary](http://go.wuhaolin.cn/) 70 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Test](https://github.com/gwuhaolin/livego/workflows/Test/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest) 6 | [![Release](https://github.com/gwuhaolin/livego/workflows/Release/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease) 7 | 8 | 简单高效的直播服务器: 9 | - 安装和使用非常简单; 10 | - 纯 Golang 编写,性能高,跨平台; 11 | - 支持常用的传输协议、文件格式、编码格式; 12 | 13 | #### 支持的传输协议 14 | - RTMP 15 | - AMF 16 | - HLS 17 | - HTTP-FLV 18 | 19 | #### 支持的容器格式 20 | - FLV 21 | - TS 22 | 23 | #### 支持的编码格式 24 | - H264 25 | - AAC 26 | - MP3 27 | 28 | ## 安装 29 | 直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后,在命令行中执行。 30 | 31 | #### 从 Docker 启动 32 | 执行`docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego`启动 33 | 34 | #### 从源码编译 35 | 1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git` 36 | 2. 去 livego 目录中 执行 `go build` 37 | 38 | ## 使用 39 | 1. 启动服务:执行 `livego` 二进制文件启动 livego 服务; 40 | 2. 访问 `http://localhost:8090/control/get?room=movie` 获取一个房间的 channelkey(channelkey用于推流,movie用于播放). 41 | 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)); 42 | 4. 播放: 支持多种播放协议,播放地址如下: 43 | - `RTMP`:`rtmp://localhost:1935/{appname}/movie` 44 | - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv` 45 | - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8` 46 | 47 | 所有配置项: 48 | ```bash 49 | ./livego -h 50 | Usage of ./livego: 51 | --api_addr string HTTP管理访问监听地址 (default ":8090") 52 | --config_file string 配置文件路径 (默认 "livego.yaml") 53 | --flv_dir string 输出的 flv 文件路径 flvDir/APP/KEY_TIME.flv (默认 "tmp") 54 | --gop_num int gop 数量 (default 1) 55 | --hls_addr string HLS 服务监听地址 (默认 ":7002") 56 | --hls_keep_after_end Maintains the HLS after the stream ends 57 | --httpflv_addr string HTTP-FLV server listen address (默认 ":7001") 58 | --level string 日志等级 (默认 "info") 59 | --read_timeout int 读超时时间 (默认 10) 60 | --rtmp_addr string RTMP 服务监听地址 (默认 ":1935") 61 | --write_timeout int 写超时时间 (默认 10) 62 | ``` 63 | 64 | ### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3) 65 | 66 | 对Golang感兴趣?请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/) 67 | 68 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /av/av.go: -------------------------------------------------------------------------------- 1 | package av 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | const ( 9 | TAG_AUDIO = 8 10 | TAG_VIDEO = 9 11 | TAG_SCRIPTDATAAMF0 = 18 12 | TAG_SCRIPTDATAAMF3 = 0xf 13 | ) 14 | 15 | const ( 16 | MetadatAMF0 = 0x12 17 | MetadataAMF3 = 0xf 18 | ) 19 | 20 | const ( 21 | SOUND_MP3 = 2 22 | SOUND_NELLYMOSER_16KHZ_MONO = 4 23 | SOUND_NELLYMOSER_8KHZ_MONO = 5 24 | SOUND_NELLYMOSER = 6 25 | SOUND_ALAW = 7 26 | SOUND_MULAW = 8 27 | SOUND_AAC = 10 28 | SOUND_SPEEX = 11 29 | 30 | SOUND_5_5Khz = 0 31 | SOUND_11Khz = 1 32 | SOUND_22Khz = 2 33 | SOUND_44Khz = 3 34 | 35 | SOUND_8BIT = 0 36 | SOUND_16BIT = 1 37 | 38 | SOUND_MONO = 0 39 | SOUND_STEREO = 1 40 | 41 | AAC_SEQHDR = 0 42 | AAC_RAW = 1 43 | ) 44 | 45 | const ( 46 | AVC_SEQHDR = 0 47 | AVC_NALU = 1 48 | AVC_EOS = 2 49 | 50 | FRAME_KEY = 1 51 | FRAME_INTER = 2 52 | 53 | VIDEO_H264 = 7 54 | ) 55 | 56 | var ( 57 | PUBLISH = "publish" 58 | PLAY = "play" 59 | ) 60 | 61 | // Header can be converted to AudioHeaderInfo or VideoHeaderInfo 62 | type Packet struct { 63 | IsAudio bool 64 | IsVideo bool 65 | IsMetadata bool 66 | TimeStamp uint32 // dts 67 | StreamID uint32 68 | Header PacketHeader 69 | Data []byte 70 | } 71 | 72 | type PacketHeader interface { 73 | } 74 | 75 | type AudioPacketHeader interface { 76 | PacketHeader 77 | SoundFormat() uint8 78 | AACPacketType() uint8 79 | } 80 | 81 | type VideoPacketHeader interface { 82 | PacketHeader 83 | IsKeyFrame() bool 84 | IsSeq() bool 85 | CodecID() uint8 86 | CompositionTime() int32 87 | } 88 | 89 | type Demuxer interface { 90 | Demux(*Packet) (ret *Packet, err error) 91 | } 92 | 93 | type Muxer interface { 94 | Mux(*Packet, io.Writer) error 95 | } 96 | 97 | type SampleRater interface { 98 | SampleRate() (int, error) 99 | } 100 | 101 | type CodecParser interface { 102 | SampleRater 103 | Parse(*Packet, io.Writer) error 104 | } 105 | 106 | type GetWriter interface { 107 | GetWriter(Info) WriteCloser 108 | } 109 | 110 | type Handler interface { 111 | HandleReader(ReadCloser) 112 | HandleWriter(WriteCloser) 113 | } 114 | 115 | type Alive interface { 116 | Alive() bool 117 | } 118 | 119 | type Closer interface { 120 | Info() Info 121 | Close(error) 122 | } 123 | 124 | type CalcTime interface { 125 | CalcBaseTimestamp() 126 | } 127 | 128 | type Info struct { 129 | Key string 130 | URL string 131 | UID string 132 | Inter bool 133 | } 134 | 135 | func (info Info) IsInterval() bool { 136 | return info.Inter 137 | } 138 | 139 | func (info Info) String() string { 140 | return fmt.Sprintf("", 141 | info.Key, info.URL, info.UID, info.Inter) 142 | } 143 | 144 | type ReadCloser interface { 145 | Closer 146 | Alive 147 | Read(*Packet) error 148 | } 149 | 150 | type WriteCloser interface { 151 | Closer 152 | Alive 153 | CalcTime 154 | Write(*Packet) error 155 | } 156 | -------------------------------------------------------------------------------- /av/rwbase.go: -------------------------------------------------------------------------------- 1 | package av 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type RWBaser struct { 9 | lock sync.Mutex 10 | timeout time.Duration 11 | PreTime time.Time 12 | BaseTimestamp uint32 13 | LastVideoTimestamp uint32 14 | LastAudioTimestamp uint32 15 | } 16 | 17 | func NewRWBaser(duration time.Duration) RWBaser { 18 | return RWBaser{ 19 | timeout: duration, 20 | PreTime: time.Now(), 21 | } 22 | } 23 | 24 | func (rw *RWBaser) BaseTimeStamp() uint32 { 25 | return rw.BaseTimestamp 26 | } 27 | 28 | func (rw *RWBaser) CalcBaseTimestamp() { 29 | if rw.LastAudioTimestamp > rw.LastVideoTimestamp { 30 | rw.BaseTimestamp = rw.LastAudioTimestamp 31 | } else { 32 | rw.BaseTimestamp = rw.LastVideoTimestamp 33 | } 34 | } 35 | 36 | func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) { 37 | if typeID == TAG_VIDEO { 38 | rw.LastVideoTimestamp = timestamp 39 | } else if typeID == TAG_AUDIO { 40 | rw.LastAudioTimestamp = timestamp 41 | } 42 | } 43 | 44 | func (rw *RWBaser) SetPreTime() { 45 | rw.lock.Lock() 46 | rw.PreTime = time.Now() 47 | rw.lock.Unlock() 48 | } 49 | 50 | func (rw *RWBaser) Alive() bool { 51 | rw.lock.Lock() 52 | b := !(time.Now().Sub(rw.PreTime) >= rw.timeout) 53 | rw.lock.Unlock() 54 | return b 55 | } 56 | -------------------------------------------------------------------------------- /configure/channel.go: -------------------------------------------------------------------------------- 1 | package configure 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gwuhaolin/livego/utils/uid" 7 | 8 | "github.com/go-redis/redis/v7" 9 | "github.com/patrickmn/go-cache" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type RoomKeysType struct { 14 | redisCli *redis.Client 15 | localCache *cache.Cache 16 | } 17 | 18 | var RoomKeys = &RoomKeysType{ 19 | localCache: cache.New(cache.NoExpiration, 0), 20 | } 21 | 22 | var saveInLocal = true 23 | 24 | func Init() { 25 | saveInLocal = len(Config.GetString("redis_addr")) == 0 26 | if saveInLocal { 27 | return 28 | } 29 | 30 | RoomKeys.redisCli = redis.NewClient(&redis.Options{ 31 | Addr: Config.GetString("redis_addr"), 32 | Password: Config.GetString("redis_pwd"), 33 | DB: 0, 34 | }) 35 | 36 | _, err := RoomKeys.redisCli.Ping().Result() 37 | if err != nil { 38 | log.Panic("Redis: ", err) 39 | } 40 | 41 | log.Info("Redis connected") 42 | } 43 | 44 | // set/reset a random key for channel 45 | func (r *RoomKeysType) SetKey(channel string) (key string, err error) { 46 | if !saveInLocal { 47 | for { 48 | key = uid.RandStringRunes(48) 49 | if _, err = r.redisCli.Get(key).Result(); err == redis.Nil { 50 | err = r.redisCli.Set(channel, key, 0).Err() 51 | if err != nil { 52 | return 53 | } 54 | 55 | err = r.redisCli.Set(key, channel, 0).Err() 56 | return 57 | } else if err != nil { 58 | return 59 | } 60 | } 61 | } 62 | 63 | for { 64 | key = uid.RandStringRunes(48) 65 | if _, found := r.localCache.Get(key); !found { 66 | r.localCache.SetDefault(channel, key) 67 | r.localCache.SetDefault(key, channel) 68 | break 69 | } 70 | } 71 | return 72 | } 73 | 74 | func (r *RoomKeysType) GetKey(channel string) (newKey string, err error) { 75 | if !saveInLocal { 76 | if newKey, err = r.redisCli.Get(channel).Result(); err == redis.Nil { 77 | newKey, err = r.SetKey(channel) 78 | log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) 79 | return 80 | } 81 | 82 | return 83 | } 84 | 85 | var key interface{} 86 | var found bool 87 | if key, found = r.localCache.Get(channel); found { 88 | return key.(string), nil 89 | } 90 | newKey, err = r.SetKey(channel) 91 | log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) 92 | return 93 | } 94 | 95 | func (r *RoomKeysType) GetChannel(key string) (channel string, err error) { 96 | if !saveInLocal { 97 | return r.redisCli.Get(key).Result() 98 | } 99 | 100 | chann, found := r.localCache.Get(key) 101 | if found { 102 | return chann.(string), nil 103 | } else { 104 | return "", fmt.Errorf("%s does not exists", key) 105 | } 106 | } 107 | 108 | func (r *RoomKeysType) DeleteChannel(channel string) bool { 109 | if !saveInLocal { 110 | return r.redisCli.Del(channel).Err() != nil 111 | } 112 | 113 | key, ok := r.localCache.Get(channel) 114 | if ok { 115 | r.localCache.Delete(channel) 116 | r.localCache.Delete(key.(string)) 117 | return true 118 | } 119 | return false 120 | } 121 | 122 | func (r *RoomKeysType) DeleteKey(key string) bool { 123 | if !saveInLocal { 124 | return r.redisCli.Del(key).Err() != nil 125 | } 126 | 127 | channel, ok := r.localCache.Get(key) 128 | if ok { 129 | r.localCache.Delete(channel.(string)) 130 | r.localCache.Delete(key) 131 | return true 132 | } 133 | return false 134 | } 135 | -------------------------------------------------------------------------------- /configure/liveconfig.go: -------------------------------------------------------------------------------- 1 | package configure 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | 8 | "github.com/kr/pretty" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/pflag" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | /* 15 | { 16 | "server": [ 17 | { 18 | "appname": "live", 19 | "live": true, 20 | "hls": true, 21 | "static_push": [] 22 | } 23 | ] 24 | } 25 | */ 26 | 27 | type Application struct { 28 | Appname string `mapstructure:"appname"` 29 | Live bool `mapstructure:"live"` 30 | Hls bool `mapstructure:"hls"` 31 | Flv bool `mapstructure:"flv"` 32 | Api bool `mapstructure:"api"` 33 | StaticPush []string `mapstructure:"static_push"` 34 | } 35 | 36 | type Applications []Application 37 | 38 | type JWT struct { 39 | Secret string `mapstructure:"secret"` 40 | Algorithm string `mapstructure:"algorithm"` 41 | } 42 | type ServerCfg struct { 43 | Level string `mapstructure:"level"` 44 | ConfigFile string `mapstructure:"config_file"` 45 | FLVArchive bool `mapstructure:"flv_archive"` 46 | FLVDir string `mapstructure:"flv_dir"` 47 | RTMPNoAuth bool `mapstructure:"rtmp_noauth"` 48 | RTMPAddr string `mapstructure:"rtmp_addr"` 49 | HTTPFLVAddr string `mapstructure:"httpflv_addr"` 50 | HLSAddr string `mapstructure:"hls_addr"` 51 | HLSKeepAfterEnd bool `mapstructure:"hls_keep_after_end"` 52 | APIAddr string `mapstructure:"api_addr"` 53 | RedisAddr string `mapstructure:"redis_addr"` 54 | RedisPwd string `mapstructure:"redis_pwd"` 55 | ReadTimeout int `mapstructure:"read_timeout"` 56 | WriteTimeout int `mapstructure:"write_timeout"` 57 | EnableTLSVerify bool `mapstructure:"enable_tls_verify"` 58 | GopNum int `mapstructure:"gop_num"` 59 | JWT JWT `mapstructure:"jwt"` 60 | Server Applications `mapstructure:"server"` 61 | } 62 | 63 | // default config 64 | var defaultConf = ServerCfg{ 65 | ConfigFile: "livego.yaml", 66 | FLVArchive: false, 67 | RTMPNoAuth: false, 68 | RTMPAddr: ":1935", 69 | HTTPFLVAddr: ":7001", 70 | HLSAddr: ":7002", 71 | HLSKeepAfterEnd: false, 72 | APIAddr: ":8090", 73 | WriteTimeout: 10, 74 | ReadTimeout: 10, 75 | EnableTLSVerify: true, 76 | GopNum: 1, 77 | Server: Applications{{ 78 | Appname: "live", 79 | Live: true, 80 | Hls: true, 81 | Flv: true, 82 | Api: true, 83 | StaticPush: nil, 84 | }}, 85 | } 86 | 87 | var ( 88 | Config = viper.New() 89 | 90 | // BypassInit can be used to bypass the init() function by setting this 91 | // value to True at compile time. 92 | // 93 | // go build -ldflags "-X 'github.com/gwuhaolin/livego/configure.BypassInit=true'" -o livego main.go 94 | BypassInit string = "" 95 | ) 96 | 97 | func initLog() { 98 | if l, err := log.ParseLevel(Config.GetString("level")); err == nil { 99 | log.SetLevel(l) 100 | log.SetReportCaller(l == log.DebugLevel) 101 | } 102 | } 103 | 104 | func init() { 105 | if BypassInit == "" { 106 | initDefault() 107 | } 108 | } 109 | 110 | func initDefault() { 111 | defer Init() 112 | 113 | // Default config 114 | b, _ := json.Marshal(defaultConf) 115 | defaultConfig := bytes.NewReader(b) 116 | viper.SetConfigType("json") 117 | viper.ReadConfig(defaultConfig) 118 | Config.MergeConfigMap(viper.AllSettings()) 119 | 120 | // Flags 121 | pflag.String("rtmp_addr", ":1935", "RTMP server listen address") 122 | pflag.Bool("enable_rtmps", false, "enable server session RTMPS") 123 | pflag.String("rtmps_cert", "server.crt", "cert file path required for RTMPS") 124 | pflag.String("rtmps_key", "server.key", "key file path required for RTMPS") 125 | pflag.String("httpflv_addr", ":7001", "HTTP-FLV server listen address") 126 | pflag.String("hls_addr", ":7002", "HLS server listen address") 127 | pflag.String("api_addr", ":8090", "HTTP manage interface server listen address") 128 | pflag.String("config_file", "livego.yaml", "configure filename") 129 | pflag.String("level", "info", "Log level") 130 | pflag.Bool("hls_keep_after_end", false, "Maintains the HLS after the stream ends") 131 | pflag.String("flv_dir", "tmp", "output flv file at flvDir/APP/KEY_TIME.flv") 132 | pflag.Int("read_timeout", 10, "read time out") 133 | pflag.Int("write_timeout", 10, "write time out") 134 | pflag.Int("gop_num", 1, "gop num") 135 | pflag.Bool("enable_tls_verify", true, "Use system root CA to verify RTMPS connection, set this flag to false on Windows") 136 | pflag.Parse() 137 | Config.BindPFlags(pflag.CommandLine) 138 | 139 | // File 140 | Config.SetConfigFile(Config.GetString("config_file")) 141 | Config.AddConfigPath(".") 142 | err := Config.ReadInConfig() 143 | if err != nil { 144 | log.Warning(err) 145 | log.Info("Using default config") 146 | } else { 147 | Config.MergeInConfig() 148 | } 149 | 150 | // Environment 151 | replacer := strings.NewReplacer(".", "_") 152 | Config.SetEnvKeyReplacer(replacer) 153 | Config.AllowEmptyEnv(true) 154 | Config.AutomaticEnv() 155 | 156 | // Log 157 | initLog() 158 | 159 | // Print final config 160 | c := ServerCfg{} 161 | Config.Unmarshal(&c) 162 | log.Debugf("Current configurations: \n%# v", pretty.Formatter(c)) 163 | } 164 | 165 | func CheckAppName(appname string) bool { 166 | apps := Applications{} 167 | Config.UnmarshalKey("server", &apps) 168 | for _, app := range apps { 169 | if app.Appname == appname { 170 | return app.Live 171 | } 172 | } 173 | return false 174 | } 175 | 176 | func GetStaticPushUrlList(appname string) ([]string, bool) { 177 | apps := Applications{} 178 | Config.UnmarshalKey("server", &apps) 179 | for _, app := range apps { 180 | if (app.Appname == appname) && app.Live { 181 | if len(app.StaticPush) > 0 { 182 | return app.StaticPush, true 183 | } else { 184 | return nil, false 185 | } 186 | } 187 | } 188 | return nil, false 189 | } 190 | -------------------------------------------------------------------------------- /container/flv/demuxer.go: -------------------------------------------------------------------------------- 1 | package flv 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gwuhaolin/livego/av" 6 | ) 7 | 8 | var ( 9 | ErrAvcEndSEQ = fmt.Errorf("avc end sequence") 10 | ) 11 | 12 | type Demuxer struct { 13 | } 14 | 15 | func NewDemuxer() *Demuxer { 16 | return &Demuxer{} 17 | } 18 | 19 | func (d *Demuxer) DemuxH(p *av.Packet) error { 20 | var tag Tag 21 | _, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo) 22 | if err != nil { 23 | return err 24 | } 25 | p.Header = &tag 26 | 27 | return nil 28 | } 29 | 30 | func (d *Demuxer) Demux(p *av.Packet) error { 31 | var tag Tag 32 | n, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo) 33 | if err != nil { 34 | return err 35 | } 36 | if tag.CodecID() == av.VIDEO_H264 && 37 | p.Data[0] == 0x17 && p.Data[1] == 0x02 { 38 | return ErrAvcEndSEQ 39 | } 40 | p.Header = &tag 41 | p.Data = p.Data[n:] 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /container/flv/muxer.go: -------------------------------------------------------------------------------- 1 | package flv 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | "time" 9 | 10 | "github.com/gwuhaolin/livego/av" 11 | "github.com/gwuhaolin/livego/configure" 12 | "github.com/gwuhaolin/livego/protocol/amf" 13 | "github.com/gwuhaolin/livego/utils/pio" 14 | "github.com/gwuhaolin/livego/utils/uid" 15 | 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | var ( 20 | flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09} 21 | ) 22 | 23 | /* 24 | func NewFlv(handler av.Handler, info av.Info) { 25 | patths := strings.SplitN(info.Key, "/", 2) 26 | 27 | if len(patths) != 2 { 28 | log.Warning("invalid info") 29 | return 30 | } 31 | 32 | w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755) 33 | if err != nil { 34 | log.Error("open file error: ", err) 35 | } 36 | 37 | writer := NewFLVWriter(patths[0], patths[1], info.URL, w) 38 | 39 | handler.HandleWriter(writer) 40 | 41 | writer.Wait() 42 | // close flv file 43 | log.Debug("close flv file") 44 | writer.ctx.Close() 45 | } 46 | */ 47 | 48 | const ( 49 | headerLen = 11 50 | ) 51 | 52 | type FLVWriter struct { 53 | Uid string 54 | av.RWBaser 55 | app, title, url string 56 | buf []byte 57 | closed chan struct{} 58 | ctx *os.File 59 | closedWriter bool 60 | } 61 | 62 | func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter { 63 | ret := &FLVWriter{ 64 | Uid: uid.NewId(), 65 | app: app, 66 | title: title, 67 | url: url, 68 | ctx: ctx, 69 | RWBaser: av.NewRWBaser(time.Second * 10), 70 | closed: make(chan struct{}), 71 | buf: make([]byte, headerLen), 72 | } 73 | 74 | ret.ctx.Write(flvHeader) 75 | pio.PutI32BE(ret.buf[:4], 0) 76 | ret.ctx.Write(ret.buf[:4]) 77 | 78 | return ret 79 | } 80 | 81 | func (writer *FLVWriter) Write(p *av.Packet) error { 82 | writer.RWBaser.SetPreTime() 83 | h := writer.buf[:headerLen] 84 | typeID := av.TAG_VIDEO 85 | if !p.IsVideo { 86 | if p.IsMetadata { 87 | var err error 88 | typeID = av.TAG_SCRIPTDATAAMF0 89 | p.Data, err = amf.MetaDataReform(p.Data, amf.DEL) 90 | if err != nil { 91 | return err 92 | } 93 | } else { 94 | typeID = av.TAG_AUDIO 95 | } 96 | } 97 | dataLen := len(p.Data) 98 | timestamp := p.TimeStamp 99 | timestamp += writer.BaseTimeStamp() 100 | writer.RWBaser.RecTimeStamp(timestamp, uint32(typeID)) 101 | 102 | preDataLen := dataLen + headerLen 103 | timestampbase := timestamp & 0xffffff 104 | timestampExt := timestamp >> 24 & 0xff 105 | 106 | pio.PutU8(h[0:1], uint8(typeID)) 107 | pio.PutI24BE(h[1:4], int32(dataLen)) 108 | pio.PutI24BE(h[4:7], int32(timestampbase)) 109 | pio.PutU8(h[7:8], uint8(timestampExt)) 110 | 111 | if _, err := writer.ctx.Write(h); err != nil { 112 | return err 113 | } 114 | 115 | if _, err := writer.ctx.Write(p.Data); err != nil { 116 | return err 117 | } 118 | 119 | pio.PutI32BE(h[:4], int32(preDataLen)) 120 | if _, err := writer.ctx.Write(h[:4]); err != nil { 121 | return err 122 | } 123 | 124 | return nil 125 | } 126 | 127 | func (writer *FLVWriter) Wait() { 128 | select { 129 | case <-writer.closed: 130 | return 131 | } 132 | } 133 | 134 | func (writer *FLVWriter) Close(error) { 135 | if writer.closedWriter { 136 | return 137 | } 138 | writer.closedWriter = true 139 | writer.ctx.Close() 140 | close(writer.closed) 141 | } 142 | 143 | func (writer *FLVWriter) Info() (ret av.Info) { 144 | ret.UID = writer.Uid 145 | ret.URL = writer.url 146 | ret.Key = writer.app + "/" + writer.title 147 | return 148 | } 149 | 150 | type FlvDvr struct{} 151 | 152 | func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser { 153 | paths := strings.SplitN(info.Key, "/", 2) 154 | if len(paths) != 2 { 155 | log.Warning("invalid info") 156 | return nil 157 | } 158 | 159 | flvDir := configure.Config.GetString("flv_dir") 160 | 161 | err := os.MkdirAll(path.Join(flvDir, paths[0]), 0755) 162 | if err != nil { 163 | log.Error("mkdir error: ", err) 164 | return nil 165 | } 166 | 167 | fileName := fmt.Sprintf("%s_%d.%s", path.Join(flvDir, info.Key), time.Now().Unix(), "flv") 168 | log.Debug("flv dvr save stream to: ", fileName) 169 | w, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0755) 170 | if err != nil { 171 | log.Error("open file error: ", err) 172 | return nil 173 | } 174 | 175 | writer := NewFLVWriter(paths[0], paths[1], info.URL, w) 176 | log.Debug("new flv dvr: ", writer.Info()) 177 | return writer 178 | } 179 | -------------------------------------------------------------------------------- /container/flv/tag.go: -------------------------------------------------------------------------------- 1 | package flv 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | ) 8 | 9 | type flvTag struct { 10 | fType uint8 11 | dataSize uint32 12 | timeStamp uint32 13 | streamID uint32 // always 0 14 | } 15 | 16 | type mediaTag struct { 17 | /* 18 | SoundFormat: UB[4] 19 | 0 = Linear PCM, platform endian 20 | 1 = ADPCM 21 | 2 = MP3 22 | 3 = Linear PCM, little endian 23 | 4 = Nellymoser 16-kHz mono 24 | 5 = Nellymoser 8-kHz mono 25 | 6 = Nellymoser 26 | 7 = G.711 A-law logarithmic PCM 27 | 8 = G.711 mu-law logarithmic PCM 28 | 9 = reserved 29 | 10 = AAC 30 | 11 = Speex 31 | 14 = MP3 8-Khz 32 | 15 = Device-specific sound 33 | Formats 7, 8, 14, and 15 are reserved for internal use 34 | AAC is supported in Flash Player 9,0,115,0 and higher. 35 | Speex is supported in Flash Player 10 and higher. 36 | */ 37 | soundFormat uint8 38 | 39 | /* 40 | SoundRate: UB[2] 41 | Sampling rate 42 | 0 = 5.5-kHz For AAC: always 3 43 | 1 = 11-kHz 44 | 2 = 22-kHz 45 | 3 = 44-kHz 46 | */ 47 | soundRate uint8 48 | 49 | /* 50 | SoundSize: UB[1] 51 | 0 = snd8Bit 52 | 1 = snd16Bit 53 | Size of each sample. 54 | This parameter only pertains to uncompressed formats. 55 | Compressed formats always decode to 16 bits internally 56 | */ 57 | soundSize uint8 58 | 59 | /* 60 | SoundType: UB[1] 61 | 0 = sndMono 62 | 1 = sndStereo 63 | Mono or stereo sound For Nellymoser: always 0 64 | For AAC: always 1 65 | */ 66 | soundType uint8 67 | 68 | /* 69 | 0: AAC sequence header 70 | 1: AAC raw 71 | */ 72 | aacPacketType uint8 73 | 74 | /* 75 | 1: keyframe (for AVC, a seekable frame) 76 | 2: inter frame (for AVC, a non- seekable frame) 77 | 3: disposable inter frame (H.263 only) 78 | 4: generated keyframe (reserved for server use only) 79 | 5: video info/command frame 80 | */ 81 | frameType uint8 82 | 83 | /* 84 | 1: JPEG (currently unused) 85 | 2: Sorenson H.263 86 | 3: Screen video 87 | 4: On2 VP6 88 | 5: On2 VP6 with alpha channel 89 | 6: Screen video version 2 90 | 7: AVC 91 | */ 92 | codecID uint8 93 | 94 | /* 95 | 0: AVC sequence header 96 | 1: AVC NALU 97 | 2: AVC end of sequence (lower level NALU sequence ender is not required or supported) 98 | */ 99 | avcPacketType uint8 100 | 101 | compositionTime int32 102 | } 103 | 104 | type Tag struct { 105 | flvt flvTag 106 | mediat mediaTag 107 | } 108 | 109 | func (tag *Tag) SoundFormat() uint8 { 110 | return tag.mediat.soundFormat 111 | } 112 | 113 | func (tag *Tag) AACPacketType() uint8 { 114 | return tag.mediat.aacPacketType 115 | } 116 | 117 | func (tag *Tag) IsKeyFrame() bool { 118 | return tag.mediat.frameType == av.FRAME_KEY 119 | } 120 | 121 | func (tag *Tag) IsSeq() bool { 122 | return tag.mediat.frameType == av.FRAME_KEY && 123 | tag.mediat.avcPacketType == av.AVC_SEQHDR 124 | } 125 | 126 | func (tag *Tag) CodecID() uint8 { 127 | return tag.mediat.codecID 128 | } 129 | 130 | func (tag *Tag) CompositionTime() int32 { 131 | return tag.mediat.compositionTime 132 | } 133 | 134 | // ParseMediaTagHeader, parse video, audio, tag header 135 | func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) { 136 | switch isVideo { 137 | case false: 138 | n, err = tag.parseAudioHeader(b) 139 | case true: 140 | n, err = tag.parseVideoHeader(b) 141 | } 142 | return 143 | } 144 | 145 | func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) { 146 | if len(b) < n+1 { 147 | err = fmt.Errorf("invalid audiodata len=%d", len(b)) 148 | return 149 | } 150 | flags := b[0] 151 | tag.mediat.soundFormat = flags >> 4 152 | tag.mediat.soundRate = (flags >> 2) & 0x3 153 | tag.mediat.soundSize = (flags >> 1) & 0x1 154 | tag.mediat.soundType = flags & 0x1 155 | n++ 156 | switch tag.mediat.soundFormat { 157 | case av.SOUND_AAC: 158 | tag.mediat.aacPacketType = b[1] 159 | n++ 160 | } 161 | return 162 | } 163 | 164 | func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) { 165 | if len(b) < n+5 { 166 | err = fmt.Errorf("invalid videodata len=%d", len(b)) 167 | return 168 | } 169 | flags := b[0] 170 | tag.mediat.frameType = flags >> 4 171 | tag.mediat.codecID = flags & 0xf 172 | n++ 173 | if tag.mediat.frameType == av.FRAME_INTER || tag.mediat.frameType == av.FRAME_KEY { 174 | tag.mediat.avcPacketType = b[1] 175 | for i := 2; i < 5; i++ { 176 | tag.mediat.compositionTime = tag.mediat.compositionTime<<8 + int32(b[i]) 177 | } 178 | n += 4 179 | } 180 | return 181 | } 182 | -------------------------------------------------------------------------------- /container/ts/crc32.go: -------------------------------------------------------------------------------- 1 | package ts 2 | 3 | func GenCrc32(src []byte) uint32 { 4 | crcTable := []uint32{ 5 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 6 | 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 7 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 8 | 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 9 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 10 | 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 11 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 12 | 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 13 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 14 | 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 15 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 16 | 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 17 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 18 | 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 19 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 20 | 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 21 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 22 | 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 23 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 24 | 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 25 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 26 | 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 27 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 28 | 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 29 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 30 | 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 31 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 32 | 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 33 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 34 | 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 35 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 36 | 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 37 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 38 | 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 39 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 40 | 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 41 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 42 | 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 43 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 44 | 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 45 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 46 | 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 47 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 48 | 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 49 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 50 | 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 51 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 52 | 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 53 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 54 | 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 55 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 56 | 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 57 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 58 | 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 59 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 60 | 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 61 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 62 | 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 63 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 64 | 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 65 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 66 | 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 67 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 68 | 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4} 69 | 70 | j := byte(0) 71 | crc32 := uint32(0xFFFFFFFF) 72 | for i := 0; i < len(src); i++ { 73 | j = (byte(crc32>>24) ^ src[i]) & 0xff 74 | crc32 = uint32(uint32(crc32<<8) ^ uint32(crcTable[j])) 75 | } 76 | 77 | return crc32 78 | } 79 | -------------------------------------------------------------------------------- /container/ts/muxer.go: -------------------------------------------------------------------------------- 1 | package ts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | ) 8 | 9 | const ( 10 | tsDefaultDataLen = 184 11 | tsPacketLen = 188 12 | h264DefaultHZ = 90 13 | 14 | videoPID = 0x100 15 | audioPID = 0x101 16 | videoSID = 0xe0 17 | audioSID = 0xc0 18 | ) 19 | 20 | type Muxer struct { 21 | videoCc byte 22 | audioCc byte 23 | patCc byte 24 | pmtCc byte 25 | pat [tsPacketLen]byte 26 | pmt [tsPacketLen]byte 27 | tsPacket [tsPacketLen]byte 28 | } 29 | 30 | func NewMuxer() *Muxer { 31 | return &Muxer{} 32 | } 33 | 34 | func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error { 35 | first := true 36 | wBytes := 0 37 | pesIndex := 0 38 | tmpLen := byte(0) 39 | dataLen := byte(0) 40 | 41 | var pes pesHeader 42 | dts := int64(p.TimeStamp) * int64(h264DefaultHZ) 43 | pts := dts 44 | pid := audioPID 45 | var videoH av.VideoPacketHeader 46 | if p.IsVideo { 47 | pid = videoPID 48 | videoH, _ = p.Header.(av.VideoPacketHeader) 49 | pts = dts + int64(videoH.CompositionTime())*int64(h264DefaultHZ) 50 | } 51 | err := pes.packet(p, pts, dts) 52 | if err != nil { 53 | return err 54 | } 55 | pesHeaderLen := pes.len 56 | packetBytesLen := len(p.Data) + int(pesHeaderLen) 57 | 58 | for { 59 | if packetBytesLen <= 0 { 60 | break 61 | } 62 | if p.IsVideo { 63 | muxer.videoCc++ 64 | if muxer.videoCc > 0xf { 65 | muxer.videoCc = 0 66 | } 67 | } else { 68 | muxer.audioCc++ 69 | if muxer.audioCc > 0xf { 70 | muxer.audioCc = 0 71 | } 72 | } 73 | 74 | i := byte(0) 75 | 76 | //sync byte 77 | muxer.tsPacket[i] = 0x47 78 | i++ 79 | 80 | //error indicator, unit start indicator,ts priority,pid 81 | muxer.tsPacket[i] = byte(pid >> 8) //pid high 5 bits 82 | if first { 83 | muxer.tsPacket[i] = muxer.tsPacket[i] | 0x40 //unit start indicator 84 | } 85 | i++ 86 | 87 | //pid low 8 bits 88 | muxer.tsPacket[i] = byte(pid) 89 | i++ 90 | 91 | //scram control, adaptation control, counter 92 | if p.IsVideo { 93 | muxer.tsPacket[i] = 0x10 | byte(muxer.videoCc&0x0f) 94 | } else { 95 | muxer.tsPacket[i] = 0x10 | byte(muxer.audioCc&0x0f) 96 | } 97 | i++ 98 | 99 | //关键帧需要加pcr 100 | if first && p.IsVideo && videoH.IsKeyFrame() { 101 | muxer.tsPacket[3] |= 0x20 102 | muxer.tsPacket[i] = 7 103 | i++ 104 | muxer.tsPacket[i] = 0x50 105 | i++ 106 | muxer.writePcr(muxer.tsPacket[0:], i, dts) 107 | i += 6 108 | } 109 | 110 | //frame data 111 | if packetBytesLen >= tsDefaultDataLen { 112 | dataLen = tsDefaultDataLen 113 | if first { 114 | dataLen -= (i - 4) 115 | } 116 | } else { 117 | muxer.tsPacket[3] |= 0x20 //have adaptation 118 | remainBytes := byte(0) 119 | dataLen = byte(packetBytesLen) 120 | if first { 121 | remainBytes = tsDefaultDataLen - dataLen - (i - 4) 122 | } else { 123 | remainBytes = tsDefaultDataLen - dataLen 124 | } 125 | muxer.adaptationBufInit(muxer.tsPacket[i:], byte(remainBytes)) 126 | i += remainBytes 127 | } 128 | if first && i < tsPacketLen && pesHeaderLen > 0 { 129 | tmpLen = tsPacketLen - i 130 | if pesHeaderLen <= tmpLen { 131 | tmpLen = pesHeaderLen 132 | } 133 | copy(muxer.tsPacket[i:], pes.data[pesIndex:pesIndex+int(tmpLen)]) 134 | i += tmpLen 135 | packetBytesLen -= int(tmpLen) 136 | dataLen -= tmpLen 137 | pesHeaderLen -= tmpLen 138 | pesIndex += int(tmpLen) 139 | } 140 | 141 | if i < tsPacketLen { 142 | tmpLen = tsPacketLen - i 143 | if tmpLen <= dataLen { 144 | dataLen = tmpLen 145 | } 146 | copy(muxer.tsPacket[i:], p.Data[wBytes:wBytes+int(dataLen)]) 147 | wBytes += int(dataLen) 148 | packetBytesLen -= int(dataLen) 149 | } 150 | if w != nil { 151 | if _, err := w.Write(muxer.tsPacket[0:]); err != nil { 152 | return err 153 | } 154 | } 155 | first = false 156 | } 157 | 158 | return nil 159 | } 160 | 161 | //PAT return pat data 162 | func (muxer *Muxer) PAT() []byte { 163 | i := 0 164 | remainByte := 0 165 | tsHeader := []byte{0x47, 0x40, 0x00, 0x10, 0x00} 166 | patHeader := []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x01} 167 | 168 | if muxer.patCc > 0xf { 169 | muxer.patCc = 0 170 | } 171 | tsHeader[3] |= muxer.patCc & 0x0f 172 | muxer.patCc++ 173 | 174 | copy(muxer.pat[i:], tsHeader) 175 | i += len(tsHeader) 176 | 177 | copy(muxer.pat[i:], patHeader) 178 | i += len(patHeader) 179 | 180 | crc32Value := GenCrc32(patHeader) 181 | muxer.pat[i] = byte(crc32Value >> 24) 182 | i++ 183 | muxer.pat[i] = byte(crc32Value >> 16) 184 | i++ 185 | muxer.pat[i] = byte(crc32Value >> 8) 186 | i++ 187 | muxer.pat[i] = byte(crc32Value) 188 | i++ 189 | 190 | remainByte = int(tsPacketLen - i) 191 | for j := 0; j < remainByte; j++ { 192 | muxer.pat[i+j] = 0xff 193 | } 194 | 195 | return muxer.pat[0:] 196 | } 197 | 198 | // PMT return pmt data 199 | func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte { 200 | i := int(0) 201 | j := int(0) 202 | var progInfo []byte 203 | remainBytes := int(0) 204 | tsHeader := []byte{0x47, 0x50, 0x01, 0x10, 0x00} 205 | pmtHeader := []byte{0x02, 0xb0, 0xff, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00} 206 | if !hasVideo { 207 | pmtHeader[9] = 0x01 208 | progInfo = []byte{0x0f, 0xe1, 0x01, 0xf0, 0x00} 209 | } else { 210 | progInfo = []byte{0x1b, 0xe1, 0x00, 0xf0, 0x00, //h264 or h265* 211 | 0x0f, 0xe1, 0x01, 0xf0, 0x00, //mp3 or aac 212 | } 213 | } 214 | pmtHeader[2] = byte(len(progInfo) + 9 + 4) 215 | 216 | if muxer.pmtCc > 0xf { 217 | muxer.pmtCc = 0 218 | } 219 | tsHeader[3] |= muxer.pmtCc & 0x0f 220 | muxer.pmtCc++ 221 | 222 | if soundFormat == 2 || 223 | soundFormat == 14 { 224 | if hasVideo { 225 | progInfo[5] = 0x4 226 | } else { 227 | progInfo[0] = 0x4 228 | } 229 | } 230 | 231 | copy(muxer.pmt[i:], tsHeader) 232 | i += len(tsHeader) 233 | 234 | copy(muxer.pmt[i:], pmtHeader) 235 | i += len(pmtHeader) 236 | 237 | copy(muxer.pmt[i:], progInfo[0:]) 238 | i += len(progInfo) 239 | 240 | crc32Value := GenCrc32(muxer.pmt[5 : 5+len(pmtHeader)+len(progInfo)]) 241 | muxer.pmt[i] = byte(crc32Value >> 24) 242 | i++ 243 | muxer.pmt[i] = byte(crc32Value >> 16) 244 | i++ 245 | muxer.pmt[i] = byte(crc32Value >> 8) 246 | i++ 247 | muxer.pmt[i] = byte(crc32Value) 248 | i++ 249 | 250 | remainBytes = int(tsPacketLen - i) 251 | for j = 0; j < remainBytes; j++ { 252 | muxer.pmt[i+j] = 0xff 253 | } 254 | 255 | return muxer.pmt[0:] 256 | } 257 | 258 | func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) { 259 | src[0] = byte(remainBytes - 1) 260 | if remainBytes == 1 { 261 | } else { 262 | src[1] = 0x00 263 | for i := 2; i < len(src); i++ { 264 | src[i] = 0xff 265 | } 266 | } 267 | return 268 | } 269 | 270 | func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error { 271 | b[i] = byte(pcr >> 25) 272 | i++ 273 | b[i] = byte((pcr >> 17) & 0xff) 274 | i++ 275 | b[i] = byte((pcr >> 9) & 0xff) 276 | i++ 277 | b[i] = byte((pcr >> 1) & 0xff) 278 | i++ 279 | b[i] = byte(((pcr & 0x1) << 7) | 0x7e) 280 | i++ 281 | b[i] = 0x00 282 | 283 | return nil 284 | } 285 | 286 | type pesHeader struct { 287 | len byte 288 | data [tsPacketLen]byte 289 | } 290 | 291 | // packet return pes packet 292 | func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error { 293 | //PES header 294 | i := 0 295 | header.data[i] = 0x00 296 | i++ 297 | header.data[i] = 0x00 298 | i++ 299 | header.data[i] = 0x01 300 | i++ 301 | 302 | sid := audioSID 303 | if p.IsVideo { 304 | sid = videoSID 305 | } 306 | header.data[i] = byte(sid) 307 | i++ 308 | 309 | flag := 0x80 310 | ptslen := 5 311 | dtslen := ptslen 312 | headerSize := ptslen 313 | if p.IsVideo && pts != dts { 314 | flag |= 0x40 315 | headerSize += 5 //add dts 316 | } 317 | size := len(p.Data) + headerSize + 3 318 | if size > 0xffff { 319 | size = 0 320 | } 321 | header.data[i] = byte(size >> 8) 322 | i++ 323 | header.data[i] = byte(size) 324 | i++ 325 | 326 | header.data[i] = 0x80 327 | i++ 328 | header.data[i] = byte(flag) 329 | i++ 330 | header.data[i] = byte(headerSize) 331 | i++ 332 | 333 | header.writeTs(header.data[0:], i, flag>>6, pts) 334 | i += ptslen 335 | if p.IsVideo && pts != dts { 336 | header.writeTs(header.data[0:], i, 1, dts) 337 | i += dtslen 338 | } 339 | 340 | header.len = byte(i) 341 | 342 | return nil 343 | } 344 | 345 | func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) { 346 | val := uint32(0) 347 | if ts > 0x1ffffffff { 348 | ts -= 0x1ffffffff 349 | } 350 | val = uint32(fb<<4) | ((uint32(ts>>30) & 0x07) << 1) | 1 351 | src[i] = byte(val) 352 | i++ 353 | 354 | val = ((uint32(ts>>15) & 0x7fff) << 1) | 1 355 | src[i] = byte(val >> 8) 356 | i++ 357 | src[i] = byte(val) 358 | i++ 359 | 360 | val = (uint32(ts&0x7fff) << 1) | 1 361 | src[i] = byte(val >> 8) 362 | i++ 363 | src[i] = byte(val) 364 | } 365 | -------------------------------------------------------------------------------- /container/ts/muxer_test.go: -------------------------------------------------------------------------------- 1 | package ts 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type TestWriter struct { 12 | buf []byte 13 | count int 14 | } 15 | 16 | //Write write p to w.buf 17 | func (w *TestWriter) Write(p []byte) (int, error) { 18 | w.count++ 19 | w.buf = p 20 | return len(p), nil 21 | } 22 | 23 | func TestTSEncoder(t *testing.T) { 24 | at := assert.New(t) 25 | m := NewMuxer() 26 | 27 | w := &TestWriter{} 28 | data := []byte{0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81, 29 | 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7, 30 | 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38, 31 | } 32 | p := av.Packet{ 33 | IsVideo: false, 34 | Data: data, 35 | } 36 | err := m.Mux(&p, w) 37 | at.Equal(err, nil) 38 | at.Equal(w.count, 1) 39 | at.Equal(w.buf, []byte{0x47, 0x41, 0x01, 0x31, 0x81, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 41 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 42 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 43 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 44 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 45 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 46 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 47 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 48 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x30, 49 | 0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01, 0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 50 | 0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 51 | 0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 52 | 0x06, 0x00, 0x38}) 53 | } 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gwuhaolin/livego 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/go-redis/redis/v7 v7.2.0 9 | github.com/gorilla/mux v1.7.4 // indirect 10 | github.com/kr/pretty v0.1.0 11 | github.com/patrickmn/go-cache v2.1.0+incompatible 12 | github.com/satori/go.uuid v1.2.0 13 | github.com/sirupsen/logrus v1.5.0 14 | github.com/spf13/pflag v1.0.3 15 | github.com/spf13/viper v1.6.3 16 | github.com/stretchr/testify v1.4.0 17 | github.com/urfave/negroni v1.0.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /livego.yaml: -------------------------------------------------------------------------------- 1 | # # Logger level 2 | # level: info 3 | 4 | # # FLV Options 5 | # flv_archive: false 6 | # flv_dir: "./tmp" 7 | # httpflv_addr: ":7001" 8 | 9 | # # RTMP Options 10 | # rtmp_noauth: false 11 | # rtmp_addr: ":1935" 12 | # enable_rtmps: true 13 | # rtmps_cert: server.crt 14 | # rtmps_key: server.key 15 | # read_timeout: 10 16 | # write_timeout: 10 17 | 18 | # # HLS Options 19 | # hls_addr: ":7002" 20 | #use_hls_https: true 21 | 22 | # # API Options 23 | # api_addr: ":8090" 24 | server: 25 | - appname: live 26 | live: true 27 | hls: true 28 | api: true 29 | flv: true 30 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwuhaolin/livego/16c6af5d90317eeb6709ab1218b01350ac7ec2e5/logo.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "path" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/gwuhaolin/livego/configure" 12 | "github.com/gwuhaolin/livego/protocol/api" 13 | "github.com/gwuhaolin/livego/protocol/hls" 14 | "github.com/gwuhaolin/livego/protocol/httpflv" 15 | "github.com/gwuhaolin/livego/protocol/rtmp" 16 | 17 | log "github.com/sirupsen/logrus" 18 | ) 19 | 20 | var VERSION = "master" 21 | 22 | func startHls() *hls.Server { 23 | hlsAddr := configure.Config.GetString("hls_addr") 24 | hlsListen, err := net.Listen("tcp", hlsAddr) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | hlsServer := hls.NewServer() 30 | go func() { 31 | defer func() { 32 | if r := recover(); r != nil { 33 | log.Error("HLS server panic: ", r) 34 | } 35 | }() 36 | log.Info("HLS listen On ", hlsAddr) 37 | hlsServer.Serve(hlsListen) 38 | }() 39 | return hlsServer 40 | } 41 | 42 | func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) { 43 | rtmpAddr := configure.Config.GetString("rtmp_addr") 44 | isRtmps := configure.Config.GetBool("enable_rtmps") 45 | 46 | var rtmpListen net.Listener 47 | if isRtmps { 48 | certPath := configure.Config.GetString("rtmps_cert") 49 | keyPath := configure.Config.GetString("rtmps_key") 50 | cert, err := tls.LoadX509KeyPair(certPath, keyPath) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | rtmpListen, err = tls.Listen("tcp", rtmpAddr, &tls.Config{ 56 | Certificates: []tls.Certificate{cert}, 57 | }) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | } else { 62 | var err error 63 | rtmpListen, err = net.Listen("tcp", rtmpAddr) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | } 68 | 69 | var rtmpServer *rtmp.Server 70 | 71 | if hlsServer == nil { 72 | rtmpServer = rtmp.NewRtmpServer(stream, nil) 73 | log.Info("HLS server disable....") 74 | } else { 75 | rtmpServer = rtmp.NewRtmpServer(stream, hlsServer) 76 | log.Info("HLS server enable....") 77 | } 78 | 79 | defer func() { 80 | if r := recover(); r != nil { 81 | log.Error("RTMP server panic: ", r) 82 | } 83 | }() 84 | if isRtmps { 85 | log.Info("RTMPS Listen On ", rtmpAddr) 86 | } else { 87 | log.Info("RTMP Listen On ", rtmpAddr) 88 | } 89 | rtmpServer.Serve(rtmpListen) 90 | } 91 | 92 | func startHTTPFlv(stream *rtmp.RtmpStream) { 93 | httpflvAddr := configure.Config.GetString("httpflv_addr") 94 | 95 | flvListen, err := net.Listen("tcp", httpflvAddr) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | hdlServer := httpflv.NewServer(stream) 101 | go func() { 102 | defer func() { 103 | if r := recover(); r != nil { 104 | log.Error("HTTP-FLV server panic: ", r) 105 | } 106 | }() 107 | log.Info("HTTP-FLV listen On ", httpflvAddr) 108 | hdlServer.Serve(flvListen) 109 | }() 110 | } 111 | 112 | func startAPI(stream *rtmp.RtmpStream) { 113 | apiAddr := configure.Config.GetString("api_addr") 114 | rtmpAddr := configure.Config.GetString("rtmp_addr") 115 | 116 | if apiAddr != "" { 117 | opListen, err := net.Listen("tcp", apiAddr) 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | opServer := api.NewServer(stream, rtmpAddr) 122 | go func() { 123 | defer func() { 124 | if r := recover(); r != nil { 125 | log.Error("HTTP-API server panic: ", r) 126 | } 127 | }() 128 | log.Info("HTTP-API listen On ", apiAddr) 129 | opServer.Serve(opListen) 130 | }() 131 | } 132 | } 133 | 134 | func init() { 135 | log.SetFormatter(&log.TextFormatter{ 136 | FullTimestamp: true, 137 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 138 | filename := path.Base(f.File) 139 | return fmt.Sprintf("%s()", f.Function), fmt.Sprintf(" %s:%d", filename, f.Line) 140 | }, 141 | }) 142 | } 143 | 144 | func main() { 145 | defer func() { 146 | if r := recover(); r != nil { 147 | log.Error("livego panic: ", r) 148 | time.Sleep(1 * time.Second) 149 | } 150 | }() 151 | 152 | log.Infof(` 153 | _ _ ____ 154 | | | (_)_ _____ / ___| ___ 155 | | | | \ \ / / _ \ | _ / _ \ 156 | | |___| |\ V / __/ |_| | (_) | 157 | |_____|_| \_/ \___|\____|\___/ 158 | version: %s 159 | `, VERSION) 160 | 161 | apps := configure.Applications{} 162 | configure.Config.UnmarshalKey("server", &apps) 163 | for _, app := range apps { 164 | stream := rtmp.NewRtmpStream() 165 | var hlsServer *hls.Server 166 | if app.Hls { 167 | hlsServer = startHls() 168 | } 169 | if app.Flv { 170 | startHTTPFlv(stream) 171 | } 172 | if app.Api { 173 | startAPI(stream) 174 | } 175 | 176 | startRtmp(stream, hlsServer) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /parser/aac/parser.go: -------------------------------------------------------------------------------- 1 | package aac 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/gwuhaolin/livego/av" 8 | ) 9 | 10 | type mpegExtension struct { 11 | objectType byte 12 | sampleRate byte 13 | } 14 | 15 | type mpegCfgInfo struct { 16 | objectType byte 17 | sampleRate byte 18 | channel byte 19 | sbr byte 20 | ps byte 21 | frameLen byte 22 | exceptionLogTs int64 23 | extension *mpegExtension 24 | } 25 | 26 | var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350} 27 | 28 | var ( 29 | specificBufInvalid = fmt.Errorf("audio mpegspecific error") 30 | audioBufInvalid = fmt.Errorf("audiodata invalid") 31 | ) 32 | 33 | const ( 34 | adtsHeaderLen = 7 35 | ) 36 | 37 | type Parser struct { 38 | gettedSpecific bool 39 | adtsHeader []byte 40 | cfgInfo *mpegCfgInfo 41 | } 42 | 43 | func NewParser() *Parser { 44 | return &Parser{ 45 | gettedSpecific: false, 46 | cfgInfo: &mpegCfgInfo{}, 47 | adtsHeader: make([]byte, adtsHeaderLen), 48 | } 49 | } 50 | 51 | func (parser *Parser) specificInfo(src []byte) error { 52 | if len(src) < 2 { 53 | return specificBufInvalid 54 | } 55 | parser.gettedSpecific = true 56 | parser.cfgInfo.objectType = (src[0] >> 3) & 0xff 57 | parser.cfgInfo.sampleRate = ((src[0] & 0x07) << 1) | src[1]>>7 58 | parser.cfgInfo.channel = (src[1] >> 3) & 0x0f 59 | return nil 60 | } 61 | 62 | func (parser *Parser) adts(src []byte, w io.Writer) error { 63 | if len(src) <= 0 || !parser.gettedSpecific { 64 | return audioBufInvalid 65 | } 66 | 67 | frameLen := uint16(len(src)) + 7 68 | 69 | //first write adts header 70 | parser.adtsHeader[0] = 0xff 71 | parser.adtsHeader[1] = 0xf1 72 | 73 | parser.adtsHeader[2] &= 0x00 74 | parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.objectType-1)<<6 75 | parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.sampleRate)<<2 76 | 77 | parser.adtsHeader[3] &= 0x00 78 | parser.adtsHeader[3] = parser.adtsHeader[3] | (parser.cfgInfo.channel<<2)<<4 79 | parser.adtsHeader[3] = parser.adtsHeader[3] | byte((frameLen<<3)>>14) 80 | 81 | parser.adtsHeader[4] &= 0x00 82 | parser.adtsHeader[4] = parser.adtsHeader[4] | byte((frameLen<<5)>>8) 83 | 84 | parser.adtsHeader[5] &= 0x00 85 | parser.adtsHeader[5] = parser.adtsHeader[5] | byte(((frameLen<<13)>>13)<<5) 86 | parser.adtsHeader[5] = parser.adtsHeader[5] | (0x7C<<1)>>3 87 | parser.adtsHeader[6] = 0xfc 88 | 89 | if _, err := w.Write(parser.adtsHeader[0:]); err != nil { 90 | return err 91 | } 92 | if _, err := w.Write(src); err != nil { 93 | return err 94 | } 95 | return nil 96 | } 97 | 98 | func (parser *Parser) SampleRate() int { 99 | rate := 44100 100 | if parser.cfgInfo.sampleRate <= byte(len(aacRates)-1) { 101 | rate = aacRates[parser.cfgInfo.sampleRate] 102 | } 103 | return rate 104 | } 105 | 106 | func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (err error) { 107 | switch packetType { 108 | case av.AAC_SEQHDR: 109 | err = parser.specificInfo(b) 110 | case av.AAC_RAW: 111 | err = parser.adts(b, w) 112 | } 113 | return 114 | } 115 | -------------------------------------------------------------------------------- /parser/h264/parser.go: -------------------------------------------------------------------------------- 1 | package h264 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | const ( 10 | i_frame byte = 0 11 | p_frame byte = 1 12 | b_frame byte = 2 13 | ) 14 | 15 | const ( 16 | nalu_type_not_define byte = 0 17 | nalu_type_slice byte = 1 //slice_layer_without_partioning_rbsp() sliceheader 18 | nalu_type_dpa byte = 2 // slice_data_partition_a_layer_rbsp( ), slice_header 19 | nalu_type_dpb byte = 3 // slice_data_partition_b_layer_rbsp( ) 20 | nalu_type_dpc byte = 4 // slice_data_partition_c_layer_rbsp( ) 21 | nalu_type_idr byte = 5 // slice_layer_without_partitioning_rbsp( ),sliceheader 22 | nalu_type_sei byte = 6 //sei_rbsp( ) 23 | nalu_type_sps byte = 7 //seq_parameter_set_rbsp( ) 24 | nalu_type_pps byte = 8 //pic_parameter_set_rbsp( ) 25 | nalu_type_aud byte = 9 // access_unit_delimiter_rbsp( ) 26 | nalu_type_eoesq byte = 10 //end_of_seq_rbsp( ) 27 | nalu_type_eostream byte = 11 //end_of_stream_rbsp( ) 28 | nalu_type_filler byte = 12 //filler_data_rbsp( ) 29 | ) 30 | 31 | const ( 32 | naluBytesLen int = 4 33 | maxSpsPpsLen int = 2 * 1024 34 | ) 35 | 36 | var ( 37 | decDataNil = fmt.Errorf("dec buf is nil") 38 | spsDataError = fmt.Errorf("sps data error") 39 | ppsHeaderError = fmt.Errorf("pps header error") 40 | ppsDataError = fmt.Errorf("pps data error") 41 | naluHeaderInvalid = fmt.Errorf("nalu header invalid") 42 | videoDataInvalid = fmt.Errorf("video data not match") 43 | dataSizeNotMatch = fmt.Errorf("data size not match") 44 | naluBodyLenError = fmt.Errorf("nalu body len error") 45 | ) 46 | 47 | var startCode = []byte{0x00, 0x00, 0x00, 0x01} 48 | var naluAud = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0} 49 | 50 | type Parser struct { 51 | frameType byte 52 | specificInfo []byte 53 | pps *bytes.Buffer 54 | } 55 | 56 | type sequenceHeader struct { 57 | configVersion byte //8bits 58 | avcProfileIndication byte //8bits 59 | profileCompatility byte //8bits 60 | avcLevelIndication byte //8bits 61 | reserved1 byte //6bits 62 | naluLen byte //2bits 63 | reserved2 byte //3bits 64 | spsNum byte //5bits 65 | ppsNum byte //8bits 66 | spsLen int 67 | ppsLen int 68 | } 69 | 70 | func NewParser() *Parser { 71 | return &Parser{ 72 | pps: bytes.NewBuffer(make([]byte, maxSpsPpsLen)), 73 | } 74 | } 75 | 76 | //return value 1:sps, value2 :pps 77 | func (parser *Parser) parseSpecificInfo(src []byte) error { 78 | if len(src) < 9 { 79 | return decDataNil 80 | } 81 | sps := []byte{} 82 | pps := []byte{} 83 | 84 | var seq sequenceHeader 85 | seq.configVersion = src[0] 86 | seq.avcProfileIndication = src[1] 87 | seq.profileCompatility = src[2] 88 | seq.avcLevelIndication = src[3] 89 | seq.reserved1 = src[4] & 0xfc 90 | seq.naluLen = src[4]&0x03 + 1 91 | seq.reserved2 = src[5] >> 5 92 | 93 | //get sps 94 | seq.spsNum = src[5] & 0x1f 95 | seq.spsLen = int(src[6])<<8 | int(src[7]) 96 | 97 | if len(src[8:]) < seq.spsLen || seq.spsLen <= 0 { 98 | return spsDataError 99 | } 100 | sps = append(sps, startCode...) 101 | sps = append(sps, src[8:(8+seq.spsLen)]...) 102 | 103 | //get pps 104 | tmpBuf := src[(8 + seq.spsLen):] 105 | if len(tmpBuf) < 4 { 106 | return ppsHeaderError 107 | } 108 | seq.ppsNum = tmpBuf[0] 109 | seq.ppsLen = int(0)<<16 | int(tmpBuf[1])<<8 | int(tmpBuf[2]) 110 | if len(tmpBuf[3:]) < seq.ppsLen || seq.ppsLen <= 0 { 111 | return ppsDataError 112 | } 113 | 114 | pps = append(pps, startCode...) 115 | pps = append(pps, tmpBuf[3:]...) 116 | 117 | parser.specificInfo = append(parser.specificInfo, sps...) 118 | parser.specificInfo = append(parser.specificInfo, pps...) 119 | 120 | return nil 121 | } 122 | 123 | func (parser *Parser) isNaluHeader(src []byte) bool { 124 | if len(src) < naluBytesLen { 125 | return false 126 | } 127 | return src[0] == 0x00 && 128 | src[1] == 0x00 && 129 | src[2] == 0x00 && 130 | src[3] == 0x01 131 | } 132 | 133 | func (parser *Parser) naluSize(src []byte) (int, error) { 134 | if len(src) < naluBytesLen { 135 | return 0, fmt.Errorf("nalusizedata invalid") 136 | } 137 | buf := src[:naluBytesLen] 138 | size := int(0) 139 | for i := 0; i < len(buf); i++ { 140 | size = size<<8 + int(buf[i]) 141 | } 142 | return size, nil 143 | } 144 | 145 | func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error { 146 | dataSize := len(src) 147 | if dataSize < naluBytesLen { 148 | return videoDataInvalid 149 | } 150 | parser.pps.Reset() 151 | _, err := w.Write(naluAud) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | index := 0 157 | nalLen := 0 158 | hasSpsPps := false 159 | hasWriteSpsPps := false 160 | 161 | for dataSize > 0 { 162 | nalLen, err = parser.naluSize(src[index:]) 163 | if err != nil { 164 | return dataSizeNotMatch 165 | } 166 | index += naluBytesLen 167 | dataSize -= naluBytesLen 168 | if dataSize >= nalLen && len(src[index:]) >= nalLen && nalLen > 0 { 169 | nalType := src[index] & 0x1f 170 | switch nalType { 171 | case nalu_type_aud: 172 | case nalu_type_idr: 173 | if !hasWriteSpsPps { 174 | hasWriteSpsPps = true 175 | if !hasSpsPps { 176 | if _, err := w.Write(parser.specificInfo); err != nil { 177 | return err 178 | } 179 | } else { 180 | if _, err := w.Write(parser.pps.Bytes()); err != nil { 181 | return err 182 | } 183 | } 184 | } 185 | fallthrough 186 | case nalu_type_slice: 187 | fallthrough 188 | case nalu_type_sei: 189 | _, err := w.Write(startCode) 190 | if err != nil { 191 | return err 192 | } 193 | _, err = w.Write(src[index : index+nalLen]) 194 | if err != nil { 195 | return err 196 | } 197 | case nalu_type_sps: 198 | fallthrough 199 | case nalu_type_pps: 200 | hasSpsPps = true 201 | _, err := parser.pps.Write(startCode) 202 | if err != nil { 203 | return err 204 | } 205 | _, err = parser.pps.Write(src[index : index+nalLen]) 206 | if err != nil { 207 | return err 208 | } 209 | } 210 | index += nalLen 211 | dataSize -= nalLen 212 | } else { 213 | return naluBodyLenError 214 | } 215 | } 216 | return nil 217 | } 218 | 219 | func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err error) { 220 | switch isSeq { 221 | case true: 222 | err = parser.parseSpecificInfo(b) 223 | case false: 224 | // is annexb 225 | if parser.isNaluHeader(b) { 226 | _, err = w.Write(b) 227 | } else { 228 | err = parser.getAnnexbH264(b, w) 229 | } 230 | } 231 | return 232 | } 233 | -------------------------------------------------------------------------------- /parser/h264/parser_test.go: -------------------------------------------------------------------------------- 1 | package h264 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestH264SeqDemux(t *testing.T) { 12 | at := assert.New(t) 13 | seq := []byte{ 14 | 0x01, 0x4d, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x4d, 0x00, 15 | 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 16 | 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x01, 0x00, 17 | 0x04, 0x68, 0xde, 0x31, 0x12, 18 | } 19 | d := NewParser() 20 | w := bytes.NewBuffer(nil) 21 | err := d.Parse(seq, true, w) 22 | at.Equal(err, nil) 23 | at.Equal(d.specificInfo, []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 24 | 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f, 25 | 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12}) 26 | } 27 | 28 | func TestH264AnnexbDemux(t *testing.T) { 29 | at := assert.New(t) 30 | nalu := []byte{ 31 | 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 32 | 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 33 | 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23, 34 | } 35 | d := NewParser() 36 | w := bytes.NewBuffer(nil) 37 | err := d.Parse(nalu, false, w) 38 | at.Equal(err, nil) 39 | at.Equal(w.Len(), 41) 40 | } 41 | 42 | func TestH264NalueSizeException(t *testing.T) { 43 | at := assert.New(t) 44 | nalu := []byte{ 45 | 0x00, 0x00, 0x10, 46 | } 47 | d := NewParser() 48 | w := bytes.NewBuffer(nil) 49 | err := d.Parse(nalu, false, w) 50 | at.Equal(err, fmt.Errorf("video data not match")) 51 | } 52 | 53 | func TestH264Mp4Demux(t *testing.T) { 54 | at := assert.New(t) 55 | nalu := []byte{ 56 | 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 57 | 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x04, 0x68, 58 | 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x02, 0x65, 0x23, 59 | } 60 | d := NewParser() 61 | w := bytes.NewBuffer(nil) 62 | err := d.Parse(nalu, false, w) 63 | at.Equal(err, nil) 64 | at.Equal(w.Len(), 47) 65 | 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, 66 | 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 67 | 0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23}) 68 | } 69 | 70 | func TestH264Mp4DemuxException1(t *testing.T) { 71 | at := assert.New(t) 72 | nalu := []byte{ 73 | 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 74 | } 75 | d := NewParser() 76 | w := bytes.NewBuffer(nil) 77 | 78 | err := d.Parse(nalu, false, w) 79 | at.Equal(err, naluBodyLenError) 80 | } 81 | 82 | func TestH264Mp4DemuxException2(t *testing.T) { 83 | at := assert.New(t) 84 | nalu := []byte{ 85 | 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 86 | 0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 87 | } 88 | d := NewParser() 89 | w := bytes.NewBuffer(nil) 90 | err := d.Parse(nalu, false, w) 91 | at.Equal(err, naluBodyLenError) 92 | } 93 | -------------------------------------------------------------------------------- /parser/mp3/parser.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Parser struct { 8 | samplingFrequency int 9 | } 10 | 11 | func NewParser() *Parser { 12 | return &Parser{} 13 | } 14 | 15 | // sampling_frequency - indicates the sampling frequency, according to the following table. 16 | // '00' 44.1 kHz 17 | // '01' 48 kHz 18 | // '10' 32 kHz 19 | // '11' reserved 20 | var mp3Rates = []int{44100, 48000, 32000} 21 | var ( 22 | errMp3DataInvalid = fmt.Errorf("mp3data invalid") 23 | errIndexInvalid = fmt.Errorf("invalid rate index") 24 | ) 25 | 26 | func (parser *Parser) Parse(src []byte) error { 27 | if len(src) < 3 { 28 | return errMp3DataInvalid 29 | } 30 | index := (src[2] >> 2) & 0x3 31 | if index <= byte(len(mp3Rates)-1) { 32 | parser.samplingFrequency = mp3Rates[index] 33 | return nil 34 | } 35 | return errIndexInvalid 36 | } 37 | 38 | func (parser *Parser) SampleRate() int { 39 | if parser.samplingFrequency == 0 { 40 | parser.samplingFrequency = 44100 41 | } 42 | return parser.samplingFrequency 43 | } 44 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/gwuhaolin/livego/av" 8 | "github.com/gwuhaolin/livego/parser/aac" 9 | "github.com/gwuhaolin/livego/parser/h264" 10 | "github.com/gwuhaolin/livego/parser/mp3" 11 | ) 12 | 13 | var ( 14 | errNoAudio = fmt.Errorf("demuxer no audio") 15 | ) 16 | 17 | type CodecParser struct { 18 | aac *aac.Parser 19 | mp3 *mp3.Parser 20 | h264 *h264.Parser 21 | } 22 | 23 | func NewCodecParser() *CodecParser { 24 | return &CodecParser{} 25 | } 26 | 27 | func (codeParser *CodecParser) SampleRate() (int, error) { 28 | if codeParser.aac == nil && codeParser.mp3 == nil { 29 | return 0, errNoAudio 30 | } 31 | if codeParser.aac != nil { 32 | return codeParser.aac.SampleRate(), nil 33 | } 34 | return codeParser.mp3.SampleRate(), nil 35 | } 36 | 37 | func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err error) { 38 | 39 | switch p.IsVideo { 40 | case true: 41 | f, ok := p.Header.(av.VideoPacketHeader) 42 | if ok { 43 | if f.CodecID() == av.VIDEO_H264 { 44 | if codeParser.h264 == nil { 45 | codeParser.h264 = h264.NewParser() 46 | } 47 | err = codeParser.h264.Parse(p.Data, f.IsSeq(), w) 48 | } 49 | } 50 | case false: 51 | f, ok := p.Header.(av.AudioPacketHeader) 52 | if ok { 53 | switch f.SoundFormat() { 54 | case av.SOUND_AAC: 55 | if codeParser.aac == nil { 56 | codeParser.aac = aac.NewParser() 57 | } 58 | err = codeParser.aac.Parse(p.Data, f.AACPacketType(), w) 59 | case av.SOUND_MP3: 60 | if codeParser.mp3 == nil { 61 | codeParser.mp3 = mp3.NewParser() 62 | } 63 | err = codeParser.mp3.Parse(p.Data) 64 | } 65 | } 66 | 67 | } 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /protocol/amf/amf.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err error) { 9 | var v interface{} 10 | for { 11 | v, err = d.Decode(r, ver) 12 | if err != nil { 13 | break 14 | } 15 | ret = append(ret, v) 16 | } 17 | return 18 | } 19 | 20 | func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) { 21 | switch ver { 22 | case 0: 23 | return d.DecodeAmf0(r) 24 | case 3: 25 | return d.DecodeAmf3(r) 26 | } 27 | 28 | return nil, fmt.Errorf("decode amf: unsupported version %d", ver) 29 | } 30 | 31 | func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) { 32 | for _, v := range val { 33 | if _, err := e.Encode(w, v, ver); err != nil { 34 | return 0, err 35 | } 36 | } 37 | return 0, nil 38 | } 39 | 40 | func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error) { 41 | switch ver { 42 | case AMF0: 43 | return e.EncodeAmf0(w, val) 44 | case AMF3: 45 | return e.EncodeAmf3(w, val) 46 | } 47 | 48 | return 0, fmt.Errorf("encode amf: unsupported version %d", ver) 49 | } 50 | -------------------------------------------------------------------------------- /protocol/amf/amf_test.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func EncodeAndDecode(val interface{}, ver Version) (result interface{}, err error) { 12 | enc := new(Encoder) 13 | dec := new(Decoder) 14 | 15 | buf := new(bytes.Buffer) 16 | 17 | _, err = enc.Encode(buf, val, ver) 18 | if err != nil { 19 | return nil, fmt.Errorf("error in encode: %s", err) 20 | } 21 | 22 | result, err = dec.Decode(buf, ver) 23 | if err != nil { 24 | return nil, fmt.Errorf("error in decode: %s", err) 25 | } 26 | 27 | return 28 | } 29 | 30 | func Compare(val interface{}, ver Version, name string, t *testing.T) { 31 | result, err := EncodeAndDecode(val, ver) 32 | if err != nil { 33 | t.Errorf("%s: %s", name, err) 34 | } 35 | 36 | if !reflect.DeepEqual(val, result) { 37 | val_v := reflect.ValueOf(val) 38 | result_v := reflect.ValueOf(result) 39 | 40 | t.Errorf("%s: comparison failed between %+v (%s) and %+v (%s)", name, val, val_v.Type(), result, result_v.Type()) 41 | 42 | Dump("expected", val) 43 | Dump("got", result) 44 | } 45 | 46 | // if val != result { 47 | // t.Errorf("%s: comparison failed between %+v and %+v", name, val, result) 48 | // } 49 | } 50 | 51 | func TestAmf0Number(t *testing.T) { 52 | Compare(float64(3.14159), 0, "amf0 number float", t) 53 | Compare(float64(124567890), 0, "amf0 number high", t) 54 | Compare(float64(-34.2), 0, "amf0 number negative", t) 55 | } 56 | 57 | func TestAmf0String(t *testing.T) { 58 | Compare("a pup!", 0, "amf0 string simple", t) 59 | Compare("日本語", 0, "amf0 string utf8", t) 60 | } 61 | 62 | func TestAmf0Boolean(t *testing.T) { 63 | Compare(true, 0, "amf0 boolean true", t) 64 | Compare(false, 0, "amf0 boolean false", t) 65 | } 66 | 67 | func TestAmf0Null(t *testing.T) { 68 | Compare(nil, 0, "amf0 boolean nil", t) 69 | } 70 | 71 | func TestAmf0Object(t *testing.T) { 72 | obj := make(Object) 73 | obj["dog"] = "alfie" 74 | obj["coffee"] = true 75 | obj["drugs"] = false 76 | obj["pi"] = 3.14159 77 | 78 | res, err := EncodeAndDecode(obj, 0) 79 | if err != nil { 80 | t.Errorf("amf0 object: %s", err) 81 | } 82 | 83 | result, ok := res.(Object) 84 | if ok != true { 85 | t.Errorf("amf0 object conversion failed") 86 | } 87 | 88 | if result["dog"] != "alfie" { 89 | t.Errorf("amf0 object string: comparison failed") 90 | } 91 | 92 | if result["coffee"] != true { 93 | t.Errorf("amf0 object true: comparison failed") 94 | } 95 | 96 | if result["drugs"] != false { 97 | t.Errorf("amf0 object false: comparison failed") 98 | } 99 | 100 | if result["pi"] != float64(3.14159) { 101 | t.Errorf("amf0 object float: comparison failed") 102 | } 103 | } 104 | 105 | func TestAmf0Array(t *testing.T) { 106 | arr := [5]float64{1, 2, 3, 4, 5} 107 | 108 | res, err := EncodeAndDecode(arr, 0) 109 | if err != nil { 110 | t.Errorf("amf0 object: %s", err) 111 | } 112 | 113 | result, ok := res.(Array) 114 | if ok != true { 115 | t.Errorf("amf0 array conversion failed") 116 | } 117 | 118 | for i := 0; i < len(arr); i++ { 119 | if arr[i] != result[i] { 120 | t.Errorf("amf0 array %d comparison failed: %v / %v", i, arr[i], result[i]) 121 | } 122 | } 123 | } 124 | 125 | func TestAmf3Integer(t *testing.T) { 126 | Compare(int32(0), 3, "amf3 integer zero", t) 127 | Compare(int32(1245), 3, "amf3 integer low", t) 128 | Compare(int32(123456), 3, "amf3 integer high", t) 129 | } 130 | 131 | func TestAmf3Double(t *testing.T) { 132 | Compare(float64(3.14159), 3, "amf3 double float", t) 133 | Compare(float64(1234567890), 3, "amf3 double high", t) 134 | Compare(float64(-12345), 3, "amf3 double negative", t) 135 | } 136 | 137 | func TestAmf3String(t *testing.T) { 138 | Compare("a pup!", 0, "amf0 string simple", t) 139 | Compare("日本語", 0, "amf0 string utf8", t) 140 | } 141 | 142 | func TestAmf3Boolean(t *testing.T) { 143 | Compare(true, 3, "amf3 boolean true", t) 144 | Compare(false, 3, "amf3 boolean false", t) 145 | } 146 | 147 | func TestAmf3Null(t *testing.T) { 148 | Compare(nil, 3, "amf3 boolean nil", t) 149 | } 150 | 151 | func TestAmf3Date(t *testing.T) { 152 | t1 := time.Unix(time.Now().Unix(), 0).UTC() // nanoseconds discarded 153 | t2 := time.Date(1983, 9, 4, 12, 4, 8, 0, time.UTC) 154 | 155 | Compare(t1, 3, "amf3 date now", t) 156 | Compare(t2, 3, "amf3 date earlier", t) 157 | } 158 | 159 | func TestAmf3Array(t *testing.T) { 160 | obj := make(Object) 161 | obj["key"] = "val" 162 | 163 | var arr Array 164 | arr = append(arr, "amf") 165 | arr = append(arr, float64(2)) 166 | arr = append(arr, -34.95) 167 | arr = append(arr, true) 168 | arr = append(arr, false) 169 | 170 | res, err := EncodeAndDecode(arr, 3) 171 | if err != nil { 172 | t.Errorf("amf3 object: %s", err) 173 | } 174 | 175 | result, ok := res.(Array) 176 | if ok != true { 177 | t.Errorf("amf3 array conversion failed: %+v", res) 178 | } 179 | 180 | for i := 0; i < len(arr); i++ { 181 | if arr[i] != result[i] { 182 | t.Errorf("amf3 array %d comparison failed: %v / %v", i, arr[i], result[i]) 183 | } 184 | } 185 | } 186 | 187 | func TestAmf3ByteArray(t *testing.T) { 188 | enc := new(Encoder) 189 | dec := new(Decoder) 190 | 191 | buf := new(bytes.Buffer) 192 | 193 | expect := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x00} 194 | 195 | enc.EncodeAmf3ByteArray(buf, expect, true) 196 | 197 | result, err := dec.DecodeAmf3ByteArray(buf, true) 198 | if err != nil { 199 | t.Errorf("err: %s", err) 200 | } 201 | 202 | if bytes.Compare(result, expect) != 0 { 203 | t.Errorf("expected: %+v, got %+v", expect, buf) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /protocol/amf/const.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | const ( 8 | AMF0 = 0x00 9 | AMF3 = 0x03 10 | ) 11 | 12 | const ( 13 | AMF0_NUMBER_MARKER = 0x00 14 | AMF0_BOOLEAN_MARKER = 0x01 15 | AMF0_STRING_MARKER = 0x02 16 | AMF0_OBJECT_MARKER = 0x03 17 | AMF0_MOVIECLIP_MARKER = 0x04 18 | AMF0_NULL_MARKER = 0x05 19 | AMF0_UNDEFINED_MARKER = 0x06 20 | AMF0_REFERENCE_MARKER = 0x07 21 | AMF0_ECMA_ARRAY_MARKER = 0x08 22 | AMF0_OBJECT_END_MARKER = 0x09 23 | AMF0_STRICT_ARRAY_MARKER = 0x0a 24 | AMF0_DATE_MARKER = 0x0b 25 | AMF0_LONG_STRING_MARKER = 0x0c 26 | AMF0_UNSUPPORTED_MARKER = 0x0d 27 | AMF0_RECORDSET_MARKER = 0x0e 28 | AMF0_XML_DOCUMENT_MARKER = 0x0f 29 | AMF0_TYPED_OBJECT_MARKER = 0x10 30 | AMF0_ACMPLUS_OBJECT_MARKER = 0x11 31 | ) 32 | 33 | const ( 34 | AMF0_BOOLEAN_FALSE = 0x00 35 | AMF0_BOOLEAN_TRUE = 0x01 36 | AMF0_STRING_MAX = 65535 37 | AMF3_INTEGER_MAX = 536870911 38 | ) 39 | 40 | const ( 41 | AMF3_UNDEFINED_MARKER = 0x00 42 | AMF3_NULL_MARKER = 0x01 43 | AMF3_FALSE_MARKER = 0x02 44 | AMF3_TRUE_MARKER = 0x03 45 | AMF3_INTEGER_MARKER = 0x04 46 | AMF3_DOUBLE_MARKER = 0x05 47 | AMF3_STRING_MARKER = 0x06 48 | AMF3_XMLDOC_MARKER = 0x07 49 | AMF3_DATE_MARKER = 0x08 50 | AMF3_ARRAY_MARKER = 0x09 51 | AMF3_OBJECT_MARKER = 0x0a 52 | AMF3_XMLSTRING_MARKER = 0x0b 53 | AMF3_BYTEARRAY_MARKER = 0x0c 54 | ) 55 | 56 | type ExternalHandler func(*Decoder, io.Reader) (interface{}, error) 57 | 58 | type Decoder struct { 59 | refCache []interface{} 60 | stringRefs []string 61 | objectRefs []interface{} 62 | traitRefs []Trait 63 | externalHandlers map[string]ExternalHandler 64 | } 65 | 66 | func NewDecoder() *Decoder { 67 | return &Decoder{ 68 | externalHandlers: make(map[string]ExternalHandler), 69 | } 70 | } 71 | 72 | func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandler) { 73 | d.externalHandlers[name] = f 74 | } 75 | 76 | type Encoder struct { 77 | } 78 | 79 | type Version uint8 80 | 81 | type Array []interface{} 82 | type Object map[string]interface{} 83 | 84 | type TypedObject struct { 85 | Type string 86 | Object Object 87 | } 88 | 89 | type Trait struct { 90 | Type string 91 | Externalizable bool 92 | Dynamic bool 93 | Properties []string 94 | } 95 | 96 | func NewTrait() *Trait { 97 | return &Trait{} 98 | } 99 | 100 | func NewTypedObject() *TypedObject { 101 | return &TypedObject{ 102 | Type: "", 103 | Object: make(Object), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /protocol/amf/decoder_amf3_external.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math" 7 | ) 8 | 9 | // Abstract external boilerplate 10 | func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error) { 11 | result = make(Object) 12 | 13 | if err = d.decodeExternal(r, &result, 14 | []string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"}, 15 | []string{"clientIdBytes", "messageIdBytes"}); err != nil { 16 | return result, fmt.Errorf("unable to decode abstract external: %s", err) 17 | } 18 | 19 | return 20 | } 21 | 22 | // DSA 23 | func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error) { 24 | return d.decodeAsyncMessage(r) 25 | } 26 | func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) { 27 | result, err = d.decodeAbstractMessage(r) 28 | if err != nil { 29 | return result, fmt.Errorf("unable to decode abstract for async: %s", err) 30 | } 31 | 32 | if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil { 33 | return result, fmt.Errorf("unable to decode async external: %s", err) 34 | } 35 | 36 | return 37 | } 38 | 39 | // DSK 40 | func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err error) { 41 | return d.decodeAcknowledgeMessage(r) 42 | } 43 | func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) { 44 | result, err = d.decodeAsyncMessage(r) 45 | if err != nil { 46 | return result, fmt.Errorf("unable to decode async for ack: %s", err) 47 | } 48 | 49 | if err = d.decodeExternal(r, &result); err != nil { 50 | return result, fmt.Errorf("unable to decode ack external: %s", err) 51 | } 52 | 53 | return 54 | } 55 | 56 | // flex.messaging.io.ArrayCollection 57 | func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) { 58 | result, err := d.DecodeAmf3(r) 59 | if err != nil { 60 | return result, fmt.Errorf("cannot decode child of array collection: %s", err) 61 | } 62 | 63 | return result, nil 64 | } 65 | 66 | func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string) (err error) { 67 | var flagSet []uint8 68 | var reservedPosition uint8 69 | var fieldNames []string 70 | 71 | flagSet, err = readFlags(r) 72 | if err != nil { 73 | return fmt.Errorf("unable to read flags: %s", err) 74 | } 75 | 76 | for i, flags := range flagSet { 77 | if i < len(fieldSets) { 78 | fieldNames = fieldSets[i] 79 | } else { 80 | fieldNames = []string{} 81 | } 82 | 83 | reservedPosition = uint8(len(fieldNames)) 84 | 85 | for p, field := range fieldNames { 86 | flagBit := uint8(math.Exp2(float64(p))) 87 | if (flags & flagBit) != 0 { 88 | tmp, err := d.DecodeAmf3(r) 89 | if err != nil { 90 | return fmt.Errorf("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err) 91 | } 92 | (*obj)[field] = tmp 93 | } 94 | } 95 | 96 | if (flags >> reservedPosition) != 0 { 97 | for j := reservedPosition; j < 6; j++ { 98 | if ((flags >> j) & 0x01) != 0 { 99 | field := fmt.Sprintf("extra_%d_%d", i, j) 100 | tmp, err := d.DecodeAmf3(r) 101 | if err != nil { 102 | return fmt.Errorf("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err) 103 | } 104 | (*obj)[field] = tmp 105 | } 106 | } 107 | } 108 | } 109 | 110 | return 111 | } 112 | 113 | func readFlags(r io.Reader) (result []uint8, err error) { 114 | for { 115 | flag, err := ReadByte(r) 116 | if err != nil { 117 | return result, fmt.Errorf("unable to read flags: %s", err) 118 | } 119 | 120 | result = append(result, flag) 121 | if (flag & 0x80) == 0 { 122 | break 123 | } 124 | } 125 | 126 | return 127 | } 128 | -------------------------------------------------------------------------------- /protocol/amf/decoder_amf3_test.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | type u29TestCase struct { 9 | value uint32 10 | expect []byte 11 | } 12 | 13 | var u29TestCases = []u29TestCase{ 14 | {1, []byte{0x01}}, 15 | {2, []byte{0x02}}, 16 | {127, []byte{0x7F}}, 17 | {128, []byte{0x81, 0x00}}, 18 | {255, []byte{0x81, 0x7F}}, 19 | {256, []byte{0x82, 0x00}}, 20 | {0x3FFF, []byte{0xFF, 0x7F}}, 21 | {0x4000, []byte{0x81, 0x80, 0x00}}, 22 | {0x7FFF, []byte{0x81, 0xFF, 0x7F}}, 23 | {0x8000, []byte{0x82, 0x80, 0x00}}, 24 | {0x1FFFFF, []byte{0xFF, 0xFF, 0x7F}}, 25 | {0x200000, []byte{0x80, 0xC0, 0x80, 0x00}}, 26 | {0x3FFFFF, []byte{0x80, 0xFF, 0xFF, 0xFF}}, 27 | {0x400000, []byte{0x81, 0x80, 0x80, 0x00}}, 28 | {0x0FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF}}, 29 | } 30 | 31 | func TestDecodeAmf3Undefined(t *testing.T) { 32 | buf := bytes.NewReader([]byte{0x00}) 33 | 34 | dec := new(Decoder) 35 | 36 | got, err := dec.DecodeAmf3(buf) 37 | if err != nil { 38 | t.Errorf("%s", err) 39 | } 40 | if got != nil { 41 | t.Errorf("expect nil got %v", got) 42 | } 43 | } 44 | 45 | func TestDecodeAmf3Null(t *testing.T) { 46 | buf := bytes.NewReader([]byte{0x01}) 47 | 48 | dec := new(Decoder) 49 | 50 | got, err := dec.DecodeAmf3(buf) 51 | if err != nil { 52 | t.Errorf("%s", err) 53 | } 54 | if got != nil { 55 | t.Errorf("expect nil got %v", got) 56 | } 57 | } 58 | 59 | func TestDecodeAmf3False(t *testing.T) { 60 | buf := bytes.NewReader([]byte{0x02}) 61 | expect := false 62 | 63 | dec := new(Decoder) 64 | 65 | got, err := dec.DecodeAmf3(buf) 66 | if err != nil { 67 | t.Errorf("%s", err) 68 | } 69 | if expect != got { 70 | t.Errorf("expect %v got %v", expect, got) 71 | } 72 | } 73 | 74 | func TestDecodeAmf3True(t *testing.T) { 75 | buf := bytes.NewReader([]byte{0x03}) 76 | expect := true 77 | 78 | dec := new(Decoder) 79 | 80 | got, err := dec.DecodeAmf3(buf) 81 | if err != nil { 82 | t.Errorf("%s", err) 83 | } 84 | if expect != got { 85 | t.Errorf("expect %v got %v", expect, got) 86 | } 87 | } 88 | 89 | func TestDecodeU29(t *testing.T) { 90 | dec := new(Decoder) 91 | 92 | for _, tc := range u29TestCases { 93 | buf := bytes.NewBuffer(tc.expect) 94 | n, err := dec.decodeU29(buf) 95 | if err != nil { 96 | t.Errorf("DecodeAmf3Integer error: %s", err) 97 | } 98 | if n != tc.value { 99 | t.Errorf("DecodeAmf3Integer expect n %x got %x", tc.value, n) 100 | } 101 | } 102 | } 103 | 104 | func TestDecodeAmf3Integer(t *testing.T) { 105 | dec := new(Decoder) 106 | 107 | buf := bytes.NewReader([]byte{0x04, 0xFF, 0xFF, 0x7F}) 108 | expect := int32(2097151) 109 | 110 | got, err := dec.DecodeAmf3(buf) 111 | if err != nil { 112 | t.Errorf("%s", err) 113 | } 114 | if expect != got { 115 | t.Errorf("expect %v got %v", expect, got) 116 | } 117 | 118 | buf.Seek(0, 0) 119 | got, err = dec.DecodeAmf3Integer(buf, true) 120 | if err != nil { 121 | t.Errorf("%s", err) 122 | } 123 | if expect != got { 124 | t.Errorf("expect %v got %v", expect, got) 125 | } 126 | 127 | buf.Seek(1, 0) 128 | got, err = dec.DecodeAmf3Integer(buf, false) 129 | if err != nil { 130 | t.Errorf("%s", err) 131 | } 132 | if expect != got { 133 | t.Errorf("expect %v got %v", expect, got) 134 | } 135 | } 136 | 137 | func TestDecodeAmf3Double(t *testing.T) { 138 | buf := bytes.NewReader([]byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}) 139 | expect := float64(1.2) 140 | 141 | dec := new(Decoder) 142 | 143 | got, err := dec.DecodeAmf3(buf) 144 | if err != nil { 145 | t.Errorf("%s", err) 146 | } 147 | if expect != got { 148 | t.Errorf("expect %v got %v", expect, got) 149 | } 150 | } 151 | 152 | func TestDecodeAmf3String(t *testing.T) { 153 | buf := bytes.NewReader([]byte{0x06, 0x07, 'f', 'o', 'o'}) 154 | expect := "foo" 155 | 156 | dec := new(Decoder) 157 | 158 | got, err := dec.DecodeAmf3(buf) 159 | if err != nil { 160 | t.Errorf("%s", err) 161 | } 162 | if expect != got { 163 | t.Errorf("expect %v got %v", expect, got) 164 | } 165 | } 166 | 167 | func TestDecodeAmf3Array(t *testing.T) { 168 | buf := bytes.NewReader([]byte{0x09, 0x13, 0x01, 169 | 0x06, 0x03, '1', 170 | 0x06, 0x03, '2', 171 | 0x06, 0x03, '3', 172 | 0x06, 0x03, '4', 173 | 0x06, 0x03, '5', 174 | 0x06, 0x03, '6', 175 | 0x06, 0x03, '7', 176 | 0x06, 0x03, '8', 177 | 0x06, 0x03, '9', 178 | }) 179 | 180 | dec := new(Decoder) 181 | expect := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"} 182 | got, err := dec.DecodeAmf3Array(buf, true) 183 | if err != nil { 184 | t.Errorf("err: %s", err) 185 | } 186 | 187 | for i, v := range expect { 188 | if got[i] != v { 189 | t.Errorf("expected array element %d to be %v, got %v", i, v, got[i]) 190 | } 191 | } 192 | } 193 | 194 | func TestDecodeAmf3Object(t *testing.T) { 195 | buf := bytes.NewReader([]byte{ 196 | 0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a', 197 | 'm', 'f', '.', 'A', 'S', 'C', 'l', 'a', 198 | 's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f', 199 | 'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r', 200 | }) 201 | 202 | dec := new(Decoder) 203 | got, err := dec.DecodeAmf3(buf) 204 | if err != nil { 205 | t.Errorf("err: %s", err) 206 | } 207 | 208 | to, ok := got.(Object) 209 | if ok != true { 210 | t.Error("unable to cast object as typed object") 211 | } 212 | 213 | if to["foo"] != "bar" { 214 | t.Errorf("expected foo to be bar, got: %+v", to["foo"]) 215 | } 216 | 217 | if to["baz"] != nil { 218 | t.Errorf("expected baz to be nil, got: %+v", to["baz"]) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /protocol/amf/encoder_amf0.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | ) 9 | 10 | // amf0 polymorphic router 11 | func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) { 12 | if val == nil { 13 | return e.EncodeAmf0Null(w, true) 14 | } 15 | 16 | v := reflect.ValueOf(val) 17 | if !v.IsValid() { 18 | return e.EncodeAmf0Null(w, true) 19 | } 20 | 21 | switch v.Kind() { 22 | case reflect.String: 23 | str := v.String() 24 | if len(str) <= AMF0_STRING_MAX { 25 | return e.EncodeAmf0String(w, str, true) 26 | } else { 27 | return e.EncodeAmf0LongString(w, str, true) 28 | } 29 | case reflect.Bool: 30 | return e.EncodeAmf0Boolean(w, v.Bool(), true) 31 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 32 | return e.EncodeAmf0Number(w, float64(v.Int()), true) 33 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 34 | return e.EncodeAmf0Number(w, float64(v.Uint()), true) 35 | case reflect.Float32, reflect.Float64: 36 | return e.EncodeAmf0Number(w, float64(v.Float()), true) 37 | case reflect.Array, reflect.Slice: 38 | length := v.Len() 39 | arr := make(Array, length) 40 | for i := 0; i < length; i++ { 41 | arr[i] = v.Index(int(i)).Interface() 42 | } 43 | return e.EncodeAmf0StrictArray(w, arr, true) 44 | case reflect.Map: 45 | obj, ok := val.(Object) 46 | if ok != true { 47 | return 0, fmt.Errorf("encode amf0: unable to create object from map") 48 | } 49 | return e.EncodeAmf0Object(w, obj, true) 50 | } 51 | 52 | if _, ok := val.(TypedObject); ok { 53 | return 0, fmt.Errorf("encode amf0: unsupported type typed object") 54 | } 55 | 56 | return 0, fmt.Errorf("encode amf0: unsupported type %s", v.Type()) 57 | } 58 | 59 | // marker: 1 byte 0x00 60 | // format: 8 byte big endian float64 61 | func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarker bool) (n int, err error) { 62 | if encodeMarker { 63 | if err = WriteMarker(w, AMF0_NUMBER_MARKER); err != nil { 64 | return 65 | } 66 | n += 1 67 | } 68 | 69 | err = binary.Write(w, binary.BigEndian, &val) 70 | if err != nil { 71 | return 72 | } 73 | n += 8 74 | 75 | return 76 | } 77 | 78 | // marker: 1 byte 0x01 79 | // format: 1 byte, 0x00 = false, 0x01 = true 80 | func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker bool) (n int, err error) { 81 | if encodeMarker { 82 | if err = WriteMarker(w, AMF0_BOOLEAN_MARKER); err != nil { 83 | return 84 | } 85 | n += 1 86 | } 87 | 88 | var m int 89 | buf := make([]byte, 1) 90 | if val { 91 | buf[0] = AMF0_BOOLEAN_TRUE 92 | } else { 93 | buf[0] = AMF0_BOOLEAN_FALSE 94 | } 95 | 96 | m, err = w.Write(buf) 97 | if err != nil { 98 | return 99 | } 100 | n += m 101 | 102 | return 103 | } 104 | 105 | // marker: 1 byte 0x02 106 | // format: 107 | // - 2 byte big endian uint16 header to determine size 108 | // - n (size) byte utf8 string 109 | func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (n int, err error) { 110 | if encodeMarker { 111 | if err = WriteMarker(w, AMF0_STRING_MARKER); err != nil { 112 | return 113 | } 114 | n += 1 115 | } 116 | 117 | var m int 118 | length := uint16(len(val)) 119 | err = binary.Write(w, binary.BigEndian, length) 120 | if err != nil { 121 | return n, fmt.Errorf("encode amf0: unable to encode string length: %s", err) 122 | } 123 | n += 2 124 | 125 | m, err = w.Write([]byte(val)) 126 | if err != nil { 127 | return n, fmt.Errorf("encode amf0: unable to encode string value: %s", err) 128 | } 129 | n += m 130 | 131 | return 132 | } 133 | 134 | // marker: 1 byte 0x03 135 | // format: 136 | // - loop encoded string followed by encoded value 137 | // - terminated with empty string followed by 1 byte 0x09 138 | func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (n int, err error) { 139 | if encodeMarker { 140 | if err = WriteMarker(w, AMF0_OBJECT_MARKER); err != nil { 141 | return 142 | } 143 | n += 1 144 | } 145 | 146 | var m int 147 | for k, v := range val { 148 | m, err = e.EncodeAmf0String(w, k, false) 149 | if err != nil { 150 | return n, fmt.Errorf("encode amf0: unable to encode object key: %s", err) 151 | } 152 | n += m 153 | 154 | m, err = e.EncodeAmf0(w, v) 155 | if err != nil { 156 | return n, fmt.Errorf("encode amf0: unable to encode object value: %s", err) 157 | } 158 | n += m 159 | } 160 | 161 | m, err = e.EncodeAmf0String(w, "", false) 162 | if err != nil { 163 | return n, fmt.Errorf("encode amf0: unable to encode object empty string: %s", err) 164 | } 165 | n += m 166 | 167 | err = WriteMarker(w, AMF0_OBJECT_END_MARKER) 168 | if err != nil { 169 | return n, fmt.Errorf("encode amf0: unable to object end marker: %s", err) 170 | } 171 | n += 1 172 | 173 | return 174 | } 175 | 176 | // marker: 1 byte 0x05 177 | // no additional data 178 | func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int, err error) { 179 | if encodeMarker { 180 | if err = WriteMarker(w, AMF0_NULL_MARKER); err != nil { 181 | return 182 | } 183 | n += 1 184 | } 185 | 186 | return 187 | } 188 | 189 | // marker: 1 byte 0x06 190 | // no additional data 191 | func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n int, err error) { 192 | if encodeMarker { 193 | if err = WriteMarker(w, AMF0_UNDEFINED_MARKER); err != nil { 194 | return 195 | } 196 | n += 1 197 | } 198 | 199 | return 200 | } 201 | 202 | // marker: 1 byte 0x08 203 | // format: 204 | // - 4 byte big endian uint32 with length of associative array 205 | // - normal object format: 206 | // - loop encoded string followed by encoded value 207 | // - terminated with empty string followed by 1 byte 0x09 208 | func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool) (n int, err error) { 209 | if encodeMarker { 210 | if err = WriteMarker(w, AMF0_ECMA_ARRAY_MARKER); err != nil { 211 | return 212 | } 213 | n += 1 214 | } 215 | 216 | var m int 217 | length := uint32(len(val)) 218 | err = binary.Write(w, binary.BigEndian, length) 219 | if err != nil { 220 | return n, fmt.Errorf("encode amf0: unable to encode ecma array length: %s", err) 221 | } 222 | n += 4 223 | 224 | m, err = e.EncodeAmf0Object(w, val, false) 225 | if err != nil { 226 | return n, fmt.Errorf("encode amf0: unable to encode ecma array object: %s", err) 227 | } 228 | n += m 229 | 230 | return 231 | } 232 | 233 | // marker: 1 byte 0x0a 234 | // format: 235 | // - 4 byte big endian uint32 to determine length of associative array 236 | // - n (length) encoded values 237 | func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker bool) (n int, err error) { 238 | if encodeMarker { 239 | if err = WriteMarker(w, AMF0_STRICT_ARRAY_MARKER); err != nil { 240 | return 241 | } 242 | n += 1 243 | } 244 | 245 | var m int 246 | length := uint32(len(val)) 247 | err = binary.Write(w, binary.BigEndian, length) 248 | if err != nil { 249 | return n, fmt.Errorf("encode amf0: unable to encode strict array length: %s", err) 250 | } 251 | n += 4 252 | 253 | for _, v := range val { 254 | m, err = e.EncodeAmf0(w, v) 255 | if err != nil { 256 | return n, fmt.Errorf("encode amf0: unable to encode strict array element: %s", err) 257 | } 258 | n += m 259 | } 260 | 261 | return 262 | } 263 | 264 | // marker: 1 byte 0x0c 265 | // format: 266 | // - 4 byte big endian uint32 header to determine size 267 | // - n (size) byte utf8 string 268 | func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker bool) (n int, err error) { 269 | if encodeMarker { 270 | if err = WriteMarker(w, AMF0_LONG_STRING_MARKER); err != nil { 271 | return 272 | } 273 | n += 1 274 | } 275 | 276 | var m int 277 | length := uint32(len(val)) 278 | err = binary.Write(w, binary.BigEndian, length) 279 | if err != nil { 280 | return n, fmt.Errorf("encode amf0: unable to encode long string length: %s", err) 281 | } 282 | n += 4 283 | 284 | m, err = w.Write([]byte(val)) 285 | if err != nil { 286 | return n, fmt.Errorf("encode amf0: unable to encode long string value: %s", err) 287 | } 288 | n += m 289 | 290 | return 291 | } 292 | 293 | // marker: 1 byte 0x0d 294 | // no additional data 295 | func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) (n int, err error) { 296 | if encodeMarker { 297 | if err = WriteMarker(w, AMF0_UNSUPPORTED_MARKER); err != nil { 298 | return 299 | } 300 | n += 1 301 | } 302 | 303 | return 304 | } 305 | 306 | // marker: 1 byte 0x11 307 | func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error { 308 | return WriteMarker(w, AMF0_ACMPLUS_OBJECT_MARKER) 309 | } 310 | -------------------------------------------------------------------------------- /protocol/amf/encoder_amf0_test.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "testing" 7 | ) 8 | 9 | func TestEncodeAmf0Number(t *testing.T) { 10 | buf := new(bytes.Buffer) 11 | expect := []byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33} 12 | 13 | enc := new(Encoder) 14 | 15 | n, err := enc.EncodeAmf0(buf, float64(1.2)) 16 | if err != nil { 17 | t.Errorf("%s", err) 18 | } 19 | if n != 9 { 20 | t.Errorf("expected to write 9 bytes, actual %d", n) 21 | } 22 | if bytes.Compare(buf.Bytes(), expect) != 0 { 23 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 24 | } 25 | } 26 | 27 | func TestEncodeAmf0BooleanTrue(t *testing.T) { 28 | buf := new(bytes.Buffer) 29 | expect := []byte{0x01, 0x01} 30 | 31 | enc := new(Encoder) 32 | 33 | n, err := enc.EncodeAmf0(buf, true) 34 | if err != nil { 35 | t.Errorf("%s", err) 36 | } 37 | if n != 2 { 38 | t.Errorf("expected to write 2 bytes, actual %d", n) 39 | } 40 | if bytes.Compare(buf.Bytes(), expect) != 0 { 41 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 42 | } 43 | } 44 | 45 | func TestEncodeAmf0BooleanFalse(t *testing.T) { 46 | buf := new(bytes.Buffer) 47 | expect := []byte{0x01, 0x00} 48 | 49 | enc := new(Encoder) 50 | 51 | n, err := enc.EncodeAmf0(buf, false) 52 | if err != nil { 53 | t.Errorf("%s", err) 54 | } 55 | if n != 2 { 56 | t.Errorf("expected to write 2 bytes, actual %d", n) 57 | } 58 | if bytes.Compare(buf.Bytes(), expect) != 0 { 59 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 60 | } 61 | } 62 | 63 | func TestEncodeAmf0String(t *testing.T) { 64 | buf := new(bytes.Buffer) 65 | expect := []byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f} 66 | 67 | enc := new(Encoder) 68 | 69 | n, err := enc.EncodeAmf0(buf, "foo") 70 | if err != nil { 71 | t.Errorf("%s", err) 72 | } 73 | if n != 6 { 74 | t.Errorf("expected to write 6 bytes, actual %d", n) 75 | } 76 | if bytes.Compare(buf.Bytes(), expect) != 0 { 77 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 78 | } 79 | } 80 | 81 | func TestEncodeAmf0Object(t *testing.T) { 82 | buf := new(bytes.Buffer) 83 | expect := []byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09} 84 | 85 | enc := new(Encoder) 86 | 87 | obj := make(Object) 88 | obj["foo"] = "bar" 89 | 90 | n, err := enc.EncodeAmf0(buf, obj) 91 | if err != nil { 92 | t.Errorf("%s", err) 93 | } 94 | if n != 15 { 95 | t.Errorf("expected to write 15 bytes, actual %d", n) 96 | } 97 | if bytes.Compare(buf.Bytes(), expect) != 0 { 98 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 99 | } 100 | } 101 | 102 | func TestEncodeAmf0EcmaArray(t *testing.T) { 103 | buf := new(bytes.Buffer) 104 | expect := []byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09} 105 | 106 | enc := new(Encoder) 107 | 108 | obj := make(Object) 109 | obj["foo"] = "bar" 110 | 111 | _, err := enc.EncodeAmf0EcmaArray(buf, obj, true) 112 | if err != nil { 113 | t.Errorf("%s", err) 114 | } 115 | 116 | if bytes.Compare(buf.Bytes(), expect) != 0 { 117 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 118 | } 119 | } 120 | 121 | func TestEncodeAmf0StrictArray(t *testing.T) { 122 | buf := new(bytes.Buffer) 123 | expect := []byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05} 124 | 125 | enc := new(Encoder) 126 | 127 | arr := make(Array, 3) 128 | arr[0] = float64(5) 129 | arr[1] = "foo" 130 | arr[2] = nil 131 | 132 | _, err := enc.EncodeAmf0StrictArray(buf, arr, true) 133 | if err != nil { 134 | t.Errorf("%s", err) 135 | } 136 | 137 | if bytes.Compare(buf.Bytes(), expect) != 0 { 138 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 139 | } 140 | } 141 | 142 | func TestEncodeAmf0Null(t *testing.T) { 143 | buf := new(bytes.Buffer) 144 | expect := []byte{0x05} 145 | 146 | enc := new(Encoder) 147 | 148 | n, err := enc.EncodeAmf0(buf, nil) 149 | if err != nil { 150 | t.Errorf("%s", err) 151 | } 152 | if n != 1 { 153 | t.Errorf("expected to write 1 byte, actual %d", n) 154 | } 155 | if bytes.Compare(buf.Bytes(), expect) != 0 { 156 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 157 | } 158 | } 159 | 160 | func TestEncodeAmf0LongString(t *testing.T) { 161 | buf := new(bytes.Buffer) 162 | 163 | testBytes := []byte("12345678") 164 | 165 | tbuf := new(bytes.Buffer) 166 | for i := 0; i < 65536; i++ { 167 | tbuf.Write(testBytes) 168 | } 169 | 170 | enc := new(Encoder) 171 | 172 | _, err := enc.EncodeAmf0(buf, string(tbuf.Bytes())) 173 | if err != nil { 174 | t.Errorf("%s", err) 175 | } 176 | 177 | mbuf := make([]byte, 1) 178 | _, err = buf.Read(mbuf) 179 | if err != nil { 180 | t.Errorf("error reading header") 181 | } 182 | 183 | if mbuf[0] != 0x0c { 184 | t.Errorf("marker mismatch") 185 | } 186 | 187 | var length uint32 188 | err = binary.Read(buf, binary.BigEndian, &length) 189 | if err != nil { 190 | t.Errorf("error reading buffer") 191 | } 192 | if length != (65536 * 8) { 193 | t.Errorf("expected length to be %d, got %d", (65536 * 8), length) 194 | } 195 | 196 | tmpBuf := make([]byte, 8) 197 | counter := 0 198 | for buf.Len() > 0 { 199 | n, err := buf.Read(tmpBuf) 200 | if err != nil { 201 | t.Fatalf("test long string result check, read data(%d) error: %s, n: %d", counter, err, n) 202 | } 203 | if n != 8 { 204 | t.Fatalf("test long string result check, read data(%d) n: %d", counter, n) 205 | } 206 | if !bytes.Equal(testBytes, tmpBuf) { 207 | t.Fatalf("test long string result check, read data % x", tmpBuf) 208 | } 209 | 210 | counter++ 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /protocol/amf/encoder_amf3_test.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEncodeAmf3EmptyString(t *testing.T) { 9 | enc := new(Encoder) 10 | 11 | buf := new(bytes.Buffer) 12 | expect := []byte{0x01} 13 | 14 | _, err := enc.EncodeAmf3String(buf, "", false) 15 | if err != nil { 16 | t.Errorf("%s", err) 17 | } 18 | 19 | if bytes.Compare(buf.Bytes(), expect) != 0 { 20 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 21 | } 22 | } 23 | 24 | func TestEncodeAmf3Undefined(t *testing.T) { 25 | enc := new(Encoder) 26 | 27 | buf := new(bytes.Buffer) 28 | expect := []byte{0x00} 29 | 30 | _, err := enc.EncodeAmf3Undefined(buf, true) 31 | if err != nil { 32 | t.Errorf("%s", err) 33 | } 34 | 35 | if bytes.Compare(buf.Bytes(), expect) != 0 { 36 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 37 | } 38 | } 39 | 40 | func TestEncodeAmf3Null(t *testing.T) { 41 | enc := new(Encoder) 42 | 43 | buf := new(bytes.Buffer) 44 | expect := []byte{0x01} 45 | 46 | _, err := enc.EncodeAmf3(buf, nil) 47 | if err != nil { 48 | t.Errorf("%s", err) 49 | } 50 | 51 | if bytes.Compare(buf.Bytes(), expect) != 0 { 52 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 53 | } 54 | } 55 | 56 | func TestEncodeAmf3False(t *testing.T) { 57 | enc := new(Encoder) 58 | 59 | buf := new(bytes.Buffer) 60 | expect := []byte{0x02} 61 | 62 | _, err := enc.EncodeAmf3(buf, false) 63 | if err != nil { 64 | t.Errorf("%s", err) 65 | } 66 | 67 | if bytes.Compare(buf.Bytes(), expect) != 0 { 68 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 69 | } 70 | } 71 | 72 | func TestEncodeAmf3True(t *testing.T) { 73 | enc := new(Encoder) 74 | 75 | buf := new(bytes.Buffer) 76 | expect := []byte{0x03} 77 | 78 | _, err := enc.EncodeAmf3(buf, true) 79 | if err != nil { 80 | t.Errorf("%s", err) 81 | } 82 | 83 | if bytes.Compare(buf.Bytes(), expect) != 0 { 84 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 85 | } 86 | } 87 | 88 | func TestEncodeAmf3Integer(t *testing.T) { 89 | enc := new(Encoder) 90 | 91 | for _, tc := range u29TestCases { 92 | buf := new(bytes.Buffer) 93 | _, err := enc.EncodeAmf3Integer(buf, tc.value, false) 94 | if err != nil { 95 | t.Errorf("EncodeAmf3Integer error: %s", err) 96 | } 97 | got := buf.Bytes() 98 | if !bytes.Equal(tc.expect, got) { 99 | t.Errorf("EncodeAmf3Integer expect n %x got %x", tc.value, got) 100 | } 101 | } 102 | 103 | buf := new(bytes.Buffer) 104 | expect := []byte{0x04, 0x80, 0xFF, 0xFF, 0xFF} 105 | 106 | n, err := enc.EncodeAmf3(buf, uint32(4194303)) 107 | if err != nil { 108 | t.Errorf("%s", err) 109 | } 110 | if n != 5 { 111 | t.Errorf("expected to write 5 bytes, actual %d", n) 112 | } 113 | if bytes.Compare(buf.Bytes(), expect) != 0 { 114 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 115 | } 116 | } 117 | 118 | func TestEncodeAmf3Double(t *testing.T) { 119 | enc := new(Encoder) 120 | 121 | buf := new(bytes.Buffer) 122 | expect := []byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33} 123 | 124 | _, err := enc.EncodeAmf3(buf, float64(1.2)) 125 | if err != nil { 126 | t.Errorf("%s", err) 127 | } 128 | 129 | if bytes.Compare(buf.Bytes(), expect) != 0 { 130 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 131 | } 132 | } 133 | 134 | func TestEncodeAmf3String(t *testing.T) { 135 | enc := new(Encoder) 136 | 137 | buf := new(bytes.Buffer) 138 | expect := []byte{0x06, 0x07, 'f', 'o', 'o'} 139 | 140 | _, err := enc.EncodeAmf3(buf, "foo") 141 | if err != nil { 142 | t.Errorf("%s", err) 143 | } 144 | 145 | if bytes.Compare(buf.Bytes(), expect) != 0 { 146 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 147 | } 148 | } 149 | 150 | func TestEncodeAmf3Array(t *testing.T) { 151 | enc := new(Encoder) 152 | buf := new(bytes.Buffer) 153 | expect := []byte{0x09, 0x13, 0x01, 154 | 0x06, 0x03, '1', 155 | 0x06, 0x03, '2', 156 | 0x06, 0x03, '3', 157 | 0x06, 0x03, '4', 158 | 0x06, 0x03, '5', 159 | 0x06, 0x03, '6', 160 | 0x06, 0x03, '7', 161 | 0x06, 0x03, '8', 162 | 0x06, 0x03, '9', 163 | } 164 | 165 | arr := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"} 166 | _, err := enc.EncodeAmf3(buf, arr) 167 | if err != nil { 168 | t.Errorf("err: %s", err) 169 | } 170 | 171 | if bytes.Compare(buf.Bytes(), expect) != 0 { 172 | t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes()) 173 | } 174 | } 175 | 176 | func TestEncodeAmf3Object(t *testing.T) { 177 | enc := new(Encoder) 178 | buf := new(bytes.Buffer) 179 | expect := []byte{ 180 | 0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a', 181 | 'm', 'f', '.', 'A', 'S', 'C', 'l', 'a', 182 | 's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f', 183 | 'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r', 184 | } 185 | 186 | to := *NewTypedObject() 187 | to.Type = "org.amf.ASClass" 188 | to.Object["foo"] = "bar" 189 | to.Object["baz"] = nil 190 | 191 | _, err := enc.EncodeAmf3(buf, to) 192 | if err != nil { 193 | t.Errorf("err: %s", err) 194 | } 195 | 196 | if bytes.Compare(buf.Bytes(), expect) != 0 { 197 | t.Errorf("expected buffer:\n%#v\ngot:\n%#v", expect, buf.Bytes()) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /protocol/amf/metadata.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | const ( 11 | ADD = 0x0 12 | DEL = 0x3 13 | ) 14 | 15 | const ( 16 | SetDataFrame string = "@setDataFrame" 17 | OnMetaData string = "onMetaData" 18 | ) 19 | 20 | var setFrameFrame []byte 21 | 22 | func init() { 23 | b := bytes.NewBuffer(nil) 24 | encoder := &Encoder{} 25 | if _, err := encoder.Encode(b, SetDataFrame, AMF0); err != nil { 26 | log.Fatal(err) 27 | } 28 | setFrameFrame = b.Bytes() 29 | } 30 | 31 | func MetaDataReform(p []byte, flag uint8) ([]byte, error) { 32 | r := bytes.NewReader(p) 33 | decoder := &Decoder{} 34 | switch flag { 35 | case ADD: 36 | v, err := decoder.Decode(r, AMF0) 37 | if err != nil { 38 | return nil, err 39 | } 40 | switch v.(type) { 41 | case string: 42 | vv := v.(string) 43 | if vv != SetDataFrame { 44 | tmplen := len(setFrameFrame) 45 | b := make([]byte, tmplen+len(p)) 46 | copy(b, setFrameFrame) 47 | copy(b[tmplen:], p) 48 | p = b 49 | } 50 | default: 51 | return nil, fmt.Errorf("setFrameFrame error") 52 | } 53 | case DEL: 54 | v, err := decoder.Decode(r, AMF0) 55 | if err != nil { 56 | return nil, err 57 | } 58 | switch v.(type) { 59 | case string: 60 | vv := v.(string) 61 | if vv == SetDataFrame { 62 | p = p[len(setFrameFrame):] 63 | } 64 | default: 65 | return nil, fmt.Errorf("metadata error") 66 | } 67 | default: 68 | return nil, fmt.Errorf("invalid flag:%d", flag) 69 | } 70 | return p, nil 71 | } 72 | -------------------------------------------------------------------------------- /protocol/amf/util.go: -------------------------------------------------------------------------------- 1 | package amf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | func DumpBytes(label string, buf []byte, size int) { 10 | fmt.Printf("Dumping %s (%d bytes):\n", label, size) 11 | for i := 0; i < size; i++ { 12 | fmt.Printf("0x%02x ", buf[i]) 13 | } 14 | fmt.Printf("\n") 15 | } 16 | 17 | func Dump(label string, val interface{}) error { 18 | json, err := json.MarshalIndent(val, "", " ") 19 | if err != nil { 20 | return fmt.Errorf("Error dumping %s: %s", label, err) 21 | } 22 | 23 | fmt.Printf("Dumping %s:\n%s\n", label, json) 24 | return nil 25 | } 26 | 27 | func WriteByte(w io.Writer, b byte) (err error) { 28 | bytes := make([]byte, 1) 29 | bytes[0] = b 30 | 31 | _, err = WriteBytes(w, bytes) 32 | 33 | return 34 | } 35 | 36 | func WriteBytes(w io.Writer, bytes []byte) (int, error) { 37 | return w.Write(bytes) 38 | } 39 | 40 | func ReadByte(r io.Reader) (byte, error) { 41 | bytes, err := ReadBytes(r, 1) 42 | if err != nil { 43 | return 0x00, err 44 | } 45 | 46 | return bytes[0], nil 47 | } 48 | 49 | func ReadBytes(r io.Reader, n int) ([]byte, error) { 50 | bytes := make([]byte, n) 51 | 52 | m, err := r.Read(bytes) 53 | if err != nil { 54 | return bytes, err 55 | } 56 | 57 | if m != n { 58 | return bytes, fmt.Errorf("decode read bytes failed: expected %d got %d", m, n) 59 | } 60 | 61 | return bytes, nil 62 | } 63 | 64 | func WriteMarker(w io.Writer, m byte) error { 65 | return WriteByte(w, m) 66 | } 67 | 68 | func ReadMarker(r io.Reader) (byte, error) { 69 | return ReadByte(r) 70 | } 71 | 72 | func AssertMarker(r io.Reader, checkMarker bool, m byte) error { 73 | if checkMarker == false { 74 | return nil 75 | } 76 | 77 | marker, err := ReadMarker(r) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | if marker != m { 83 | return fmt.Errorf("decode assert marker failed: expected %v got %v", m, marker) 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /protocol/hls/align.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | const ( 4 | syncms = 2 // ms 5 | ) 6 | 7 | type align struct { 8 | frameNum uint64 9 | frameBase uint64 10 | } 11 | 12 | func (a *align) align(dts *uint64, inc uint32) { 13 | aFrameDts := *dts 14 | estPts := a.frameBase + a.frameNum*uint64(inc) 15 | var dPts uint64 16 | if estPts >= aFrameDts { 17 | dPts = estPts - aFrameDts 18 | } else { 19 | dPts = aFrameDts - estPts 20 | } 21 | 22 | if dPts <= uint64(syncms)*h264_default_hz { 23 | a.frameNum++ 24 | *dts = estPts 25 | return 26 | } 27 | a.frameNum = 1 28 | a.frameBase = aFrameDts 29 | } 30 | -------------------------------------------------------------------------------- /protocol/hls/audio_cache.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | import "bytes" 4 | 5 | const ( 6 | cache_max_frames byte = 6 7 | audio_cache_len int = 10 * 1024 8 | ) 9 | 10 | type audioCache struct { 11 | soundFormat byte 12 | num byte 13 | offset int 14 | pts uint64 15 | buf *bytes.Buffer 16 | } 17 | 18 | func newAudioCache() *audioCache { 19 | return &audioCache{ 20 | buf: bytes.NewBuffer(make([]byte, audio_cache_len)), 21 | } 22 | } 23 | 24 | func (a *audioCache) Cache(src []byte, pts uint64) bool { 25 | if a.num == 0 { 26 | a.offset = 0 27 | a.pts = pts 28 | a.buf.Reset() 29 | } 30 | a.buf.Write(src) 31 | a.offset += len(src) 32 | a.num++ 33 | 34 | return false 35 | } 36 | 37 | func (a *audioCache) GetFrame() (int, uint64, []byte) { 38 | a.num = 0 39 | return a.offset, a.pts, a.buf.Bytes() 40 | } 41 | 42 | func (a *audioCache) CacheNum() byte { 43 | return a.num 44 | } 45 | -------------------------------------------------------------------------------- /protocol/hls/cache.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | import ( 4 | "bytes" 5 | "container/list" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | const ( 11 | maxTSCacheNum = 3 12 | ) 13 | 14 | var ( 15 | ErrNoKey = fmt.Errorf("No key for cache") 16 | ) 17 | 18 | type TSCacheItem struct { 19 | id string 20 | num int 21 | lock sync.RWMutex 22 | ll *list.List 23 | lm map[string]TSItem 24 | } 25 | 26 | func NewTSCacheItem(id string) *TSCacheItem { 27 | return &TSCacheItem{ 28 | id: id, 29 | ll: list.New(), 30 | num: maxTSCacheNum, 31 | lm: make(map[string]TSItem), 32 | } 33 | } 34 | 35 | func (tcCacheItem *TSCacheItem) ID() string { 36 | return tcCacheItem.id 37 | } 38 | 39 | // TODO: found data race, fix it 40 | func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) { 41 | var seq int 42 | var getSeq bool 43 | var maxDuration int 44 | m3u8body := bytes.NewBuffer(nil) 45 | for e := tcCacheItem.ll.Front(); e != nil; e = e.Next() { 46 | key := e.Value.(string) 47 | v, ok := tcCacheItem.lm[key] 48 | if ok { 49 | if v.Duration > maxDuration { 50 | maxDuration = v.Duration 51 | } 52 | if !getSeq { 53 | getSeq = true 54 | seq = v.SeqNum 55 | } 56 | fmt.Fprintf(m3u8body, "#EXTINF:%.3f,\n%s\n", float64(v.Duration)/float64(1000), v.Name) 57 | } 58 | } 59 | w := bytes.NewBuffer(nil) 60 | fmt.Fprintf(w, 61 | "#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", 62 | maxDuration/1000+1, seq) 63 | w.Write(m3u8body.Bytes()) 64 | return w.Bytes(), nil 65 | } 66 | 67 | func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) { 68 | if tcCacheItem.ll.Len() == tcCacheItem.num { 69 | e := tcCacheItem.ll.Front() 70 | tcCacheItem.ll.Remove(e) 71 | k := e.Value.(string) 72 | delete(tcCacheItem.lm, k) 73 | } 74 | tcCacheItem.lm[key] = item 75 | tcCacheItem.ll.PushBack(key) 76 | } 77 | 78 | func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) { 79 | item, ok := tcCacheItem.lm[key] 80 | if !ok { 81 | return item, ErrNoKey 82 | } 83 | return item, nil 84 | } 85 | -------------------------------------------------------------------------------- /protocol/hls/hls.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "path" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/gwuhaolin/livego/configure" 14 | 15 | "github.com/gwuhaolin/livego/av" 16 | 17 | log "github.com/sirupsen/logrus" 18 | ) 19 | 20 | const ( 21 | duration = 3000 22 | ) 23 | 24 | var ( 25 | ErrNoPublisher = fmt.Errorf("no publisher") 26 | ErrInvalidReq = fmt.Errorf("invalid req url path") 27 | ErrNoSupportVideoCodec = fmt.Errorf("no support video codec") 28 | ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec") 29 | ) 30 | 31 | var crossdomainxml = []byte(` 32 | 33 | 34 | 35 | `) 36 | 37 | type Server struct { 38 | listener net.Listener 39 | conns *sync.Map 40 | } 41 | 42 | func NewServer() *Server { 43 | ret := &Server{ 44 | conns: &sync.Map{}, 45 | } 46 | go ret.checkStop() 47 | return ret 48 | } 49 | 50 | func (server *Server) Serve(listener net.Listener) error { 51 | mux := http.NewServeMux() 52 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 53 | server.handle(w, r) 54 | }) 55 | server.listener = listener 56 | 57 | if configure.Config.GetBool("use_hls_https") { 58 | http.ServeTLS(listener, mux, "server.crt", "server.key") 59 | } else { 60 | http.Serve(listener, mux) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (server *Server) GetWriter(info av.Info) av.WriteCloser { 67 | var s *Source 68 | v, ok := server.conns.Load(info.Key) 69 | if !ok { 70 | log.Debug("new hls source") 71 | s = NewSource(info) 72 | server.conns.Store(info.Key, s) 73 | } else { 74 | s = v.(*Source) 75 | } 76 | return s 77 | } 78 | 79 | func (server *Server) getConn(key string) *Source { 80 | v, ok := server.conns.Load(key) 81 | if !ok { 82 | return nil 83 | } 84 | return v.(*Source) 85 | } 86 | 87 | func (server *Server) checkStop() { 88 | for { 89 | <-time.After(5 * time.Second) 90 | 91 | server.conns.Range(func(key, val interface{}) bool { 92 | v := val.(*Source) 93 | if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") { 94 | log.Debug("check stop and remove: ", v.Info()) 95 | server.conns.Delete(key) 96 | } 97 | return true 98 | }) 99 | } 100 | } 101 | 102 | func (server *Server) handle(w http.ResponseWriter, r *http.Request) { 103 | if path.Base(r.URL.Path) == "crossdomain.xml" { 104 | w.Header().Set("Content-Type", "application/xml") 105 | w.Write(crossdomainxml) 106 | return 107 | } 108 | switch path.Ext(r.URL.Path) { 109 | case ".m3u8": 110 | key, _ := server.parseM3u8(r.URL.Path) 111 | conn := server.getConn(key) 112 | if conn == nil { 113 | http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) 114 | return 115 | } 116 | tsCache := conn.GetCacheInc() 117 | if tsCache == nil { 118 | http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) 119 | return 120 | } 121 | body, err := tsCache.GenM3U8PlayList() 122 | if err != nil { 123 | log.Debug("GenM3U8PlayList error: ", err) 124 | http.Error(w, err.Error(), http.StatusBadRequest) 125 | return 126 | } 127 | 128 | w.Header().Set("Access-Control-Allow-Origin", "*") 129 | w.Header().Set("Cache-Control", "no-cache") 130 | w.Header().Set("Content-Type", "application/x-mpegURL") 131 | w.Header().Set("Content-Length", strconv.Itoa(len(body))) 132 | w.Write(body) 133 | case ".ts": 134 | key, _ := server.parseTs(r.URL.Path) 135 | conn := server.getConn(key) 136 | if conn == nil { 137 | http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) 138 | return 139 | } 140 | tsCache := conn.GetCacheInc() 141 | item, err := tsCache.GetItem(r.URL.Path) 142 | if err != nil { 143 | log.Debug("GetItem error: ", err) 144 | http.Error(w, err.Error(), http.StatusBadRequest) 145 | return 146 | } 147 | w.Header().Set("Access-Control-Allow-Origin", "*") 148 | w.Header().Set("Content-Type", "video/mp2ts") 149 | w.Header().Set("Content-Length", strconv.Itoa(len(item.Data))) 150 | w.Write(item.Data) 151 | } 152 | } 153 | 154 | func (server *Server) parseM3u8(pathstr string) (key string, err error) { 155 | pathstr = strings.TrimLeft(pathstr, "/") 156 | key = strings.Split(pathstr, path.Ext(pathstr))[0] 157 | return 158 | } 159 | 160 | func (server *Server) parseTs(pathstr string) (key string, err error) { 161 | pathstr = strings.TrimLeft(pathstr, "/") 162 | paths := strings.SplitN(pathstr, "/", 3) 163 | if len(paths) != 3 { 164 | err = fmt.Errorf("invalid path=%s", pathstr) 165 | return 166 | } 167 | key = paths[0] + "/" + paths[1] 168 | 169 | return 170 | } 171 | -------------------------------------------------------------------------------- /protocol/hls/item.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | type TSItem struct { 4 | Name string 5 | SeqNum int 6 | Duration int 7 | Data []byte 8 | } 9 | 10 | func NewTSItem(name string, duration, seqNum int, b []byte) TSItem { 11 | var item TSItem 12 | item.Name = name 13 | item.SeqNum = seqNum 14 | item.Duration = duration 15 | item.Data = make([]byte, len(b)) 16 | copy(item.Data, b) 17 | return item 18 | } 19 | -------------------------------------------------------------------------------- /protocol/hls/source.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/gwuhaolin/livego/configure" 7 | "time" 8 | 9 | "github.com/gwuhaolin/livego/av" 10 | "github.com/gwuhaolin/livego/container/flv" 11 | "github.com/gwuhaolin/livego/container/ts" 12 | "github.com/gwuhaolin/livego/parser" 13 | 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | const ( 18 | videoHZ = 90000 19 | aacSampleLen = 1024 20 | maxQueueNum = 512 21 | 22 | h264_default_hz uint64 = 90 23 | ) 24 | 25 | type Source struct { 26 | av.RWBaser 27 | seq int 28 | info av.Info 29 | bwriter *bytes.Buffer 30 | btswriter *bytes.Buffer 31 | demuxer *flv.Demuxer 32 | muxer *ts.Muxer 33 | pts, dts uint64 34 | stat *status 35 | align *align 36 | cache *audioCache 37 | tsCache *TSCacheItem 38 | tsparser *parser.CodecParser 39 | closed bool 40 | packetQueue chan *av.Packet 41 | } 42 | 43 | func NewSource(info av.Info) *Source { 44 | info.Inter = true 45 | s := &Source{ 46 | info: info, 47 | align: &align{}, 48 | stat: newStatus(), 49 | RWBaser: av.NewRWBaser(time.Second * 10), 50 | cache: newAudioCache(), 51 | demuxer: flv.NewDemuxer(), 52 | muxer: ts.NewMuxer(), 53 | tsCache: NewTSCacheItem(info.Key), 54 | tsparser: parser.NewCodecParser(), 55 | bwriter: bytes.NewBuffer(make([]byte, 100*1024)), 56 | packetQueue: make(chan *av.Packet, maxQueueNum), 57 | } 58 | go func() { 59 | err := s.SendPacket() 60 | if err != nil { 61 | log.Debug("send packet error: ", err) 62 | s.closed = true 63 | } 64 | }() 65 | return s 66 | } 67 | 68 | func (source *Source) GetCacheInc() *TSCacheItem { 69 | return source.tsCache 70 | } 71 | 72 | func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) { 73 | log.Warningf("[%v] packet queue max!!!", info) 74 | for i := 0; i < maxQueueNum-84; i++ { 75 | tmpPkt, ok := <-pktQue 76 | // try to don't drop audio 77 | if ok && tmpPkt.IsAudio { 78 | if len(pktQue) > maxQueueNum-2 { 79 | <-pktQue 80 | } else { 81 | pktQue <- tmpPkt 82 | } 83 | } 84 | 85 | if ok && tmpPkt.IsVideo { 86 | videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) 87 | // dont't drop sps config and dont't drop key frame 88 | if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { 89 | pktQue <- tmpPkt 90 | } 91 | if len(pktQue) > maxQueueNum-10 { 92 | <-pktQue 93 | } 94 | } 95 | 96 | } 97 | log.Debug("packet queue len: ", len(pktQue)) 98 | } 99 | 100 | func (source *Source) Write(p *av.Packet) (err error) { 101 | err = nil 102 | if source.closed { 103 | err = fmt.Errorf("hls source closed") 104 | return 105 | } 106 | source.SetPreTime() 107 | defer func() { 108 | if e := recover(); e != nil { 109 | err = fmt.Errorf("hls source has already been closed:%v", e) 110 | } 111 | }() 112 | if len(source.packetQueue) >= maxQueueNum-24 { 113 | source.DropPacket(source.packetQueue, source.info) 114 | } else { 115 | if !source.closed { 116 | source.packetQueue <- p 117 | } 118 | } 119 | return 120 | } 121 | 122 | func (source *Source) SendPacket() error { 123 | defer func() { 124 | log.Debugf("[%v] hls sender stop", source.info) 125 | if r := recover(); r != nil { 126 | log.Warning("hls SendPacket panic: ", r) 127 | } 128 | }() 129 | 130 | log.Debugf("[%v] hls sender start", source.info) 131 | for { 132 | if source.closed { 133 | return fmt.Errorf("closed") 134 | } 135 | 136 | p, ok := <-source.packetQueue 137 | if ok { 138 | if p.IsMetadata { 139 | continue 140 | } 141 | 142 | err := source.demuxer.Demux(p) 143 | if err == flv.ErrAvcEndSEQ { 144 | log.Warning(err) 145 | continue 146 | } else { 147 | if err != nil { 148 | log.Warning(err) 149 | return err 150 | } 151 | } 152 | compositionTime, isSeq, err := source.parse(p) 153 | if err != nil { 154 | log.Warning(err) 155 | } 156 | if err != nil || isSeq { 157 | continue 158 | } 159 | if source.btswriter != nil { 160 | source.stat.update(p.IsVideo, p.TimeStamp) 161 | source.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime)) 162 | source.tsMux(p) 163 | } 164 | } else { 165 | return fmt.Errorf("closed") 166 | } 167 | } 168 | } 169 | 170 | func (source *Source) Info() (ret av.Info) { 171 | return source.info 172 | } 173 | 174 | func (source *Source) cleanup() { 175 | close(source.packetQueue) 176 | source.bwriter = nil 177 | source.btswriter = nil 178 | source.cache = nil 179 | source.tsCache = nil 180 | } 181 | 182 | func (source *Source) Close(err error) { 183 | log.Debug("hls source closed: ", source.info) 184 | if !source.closed && !configure.Config.GetBool("hls_keep_after_end") { 185 | source.cleanup() 186 | } 187 | source.closed = true 188 | } 189 | 190 | func (source *Source) cut() { 191 | newf := true 192 | if source.btswriter == nil { 193 | source.btswriter = bytes.NewBuffer(nil) 194 | } else if source.btswriter != nil && source.stat.durationMs() >= duration { 195 | source.flushAudio() 196 | 197 | source.seq++ 198 | filename := fmt.Sprintf("/%s/%d.ts", source.info.Key, time.Now().Unix()) 199 | item := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes()) 200 | source.tsCache.SetItem(filename, item) 201 | 202 | source.btswriter.Reset() 203 | source.stat.resetAndNew() 204 | } else { 205 | newf = false 206 | } 207 | if newf { 208 | source.btswriter.Write(source.muxer.PAT()) 209 | source.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true)) 210 | } 211 | } 212 | 213 | func (source *Source) parse(p *av.Packet) (int32, bool, error) { 214 | var compositionTime int32 215 | var ah av.AudioPacketHeader 216 | var vh av.VideoPacketHeader 217 | if p.IsVideo { 218 | vh = p.Header.(av.VideoPacketHeader) 219 | if vh.CodecID() != av.VIDEO_H264 { 220 | return compositionTime, false, ErrNoSupportVideoCodec 221 | } 222 | compositionTime = vh.CompositionTime() 223 | if vh.IsKeyFrame() && vh.IsSeq() { 224 | return compositionTime, true, source.tsparser.Parse(p, source.bwriter) 225 | } 226 | } else { 227 | ah = p.Header.(av.AudioPacketHeader) 228 | if ah.SoundFormat() != av.SOUND_AAC { 229 | return compositionTime, false, ErrNoSupportAudioCodec 230 | } 231 | if ah.AACPacketType() == av.AAC_SEQHDR { 232 | return compositionTime, true, source.tsparser.Parse(p, source.bwriter) 233 | } 234 | } 235 | source.bwriter.Reset() 236 | if err := source.tsparser.Parse(p, source.bwriter); err != nil { 237 | return compositionTime, false, err 238 | } 239 | p.Data = source.bwriter.Bytes() 240 | 241 | if p.IsVideo && vh.IsKeyFrame() { 242 | source.cut() 243 | } 244 | return compositionTime, false, nil 245 | } 246 | 247 | func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) { 248 | source.dts = uint64(ts) * h264_default_hz 249 | if isVideo { 250 | source.pts = source.dts + uint64(compositionTs)*h264_default_hz 251 | } else { 252 | sampleRate, _ := source.tsparser.SampleRate() 253 | source.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate)) 254 | source.pts = source.dts 255 | } 256 | } 257 | func (source *Source) flushAudio() error { 258 | return source.muxAudio(1) 259 | } 260 | 261 | func (source *Source) muxAudio(limit byte) error { 262 | if source.cache.CacheNum() < limit { 263 | return nil 264 | } 265 | var p av.Packet 266 | _, pts, buf := source.cache.GetFrame() 267 | p.Data = buf 268 | p.TimeStamp = uint32(pts / h264_default_hz) 269 | return source.muxer.Mux(&p, source.btswriter) 270 | } 271 | 272 | func (source *Source) tsMux(p *av.Packet) error { 273 | if p.IsVideo { 274 | return source.muxer.Mux(p, source.btswriter) 275 | } else { 276 | source.cache.Cache(p.Data, source.pts) 277 | return source.muxAudio(cache_max_frames) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /protocol/hls/status.go: -------------------------------------------------------------------------------- 1 | package hls 2 | 3 | import "time" 4 | 5 | type status struct { 6 | hasVideo bool 7 | seqId int64 8 | createdAt time.Time 9 | segBeginAt time.Time 10 | hasSetFirstTs bool 11 | firstTimestamp int64 12 | lastTimestamp int64 13 | } 14 | 15 | func newStatus() *status { 16 | return &status{ 17 | seqId: 0, 18 | hasSetFirstTs: false, 19 | segBeginAt: time.Now(), 20 | } 21 | } 22 | 23 | func (t *status) update(isVideo bool, timestamp uint32) { 24 | if isVideo { 25 | t.hasVideo = true 26 | } 27 | if !t.hasSetFirstTs { 28 | t.hasSetFirstTs = true 29 | t.firstTimestamp = int64(timestamp) 30 | } 31 | t.lastTimestamp = int64(timestamp) 32 | } 33 | 34 | func (t *status) resetAndNew() { 35 | t.seqId++ 36 | t.hasVideo = false 37 | t.createdAt = time.Now() 38 | t.hasSetFirstTs = false 39 | } 40 | 41 | func (t *status) durationMs() int64 { 42 | return t.lastTimestamp - t.firstTimestamp 43 | } 44 | -------------------------------------------------------------------------------- /protocol/httpflv/server.go: -------------------------------------------------------------------------------- 1 | package httpflv 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gwuhaolin/livego/av" 10 | "github.com/gwuhaolin/livego/protocol/rtmp" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | type Server struct { 16 | handler av.Handler 17 | } 18 | 19 | type stream struct { 20 | Key string `json:"key"` 21 | Id string `json:"id"` 22 | } 23 | 24 | type streams struct { 25 | Publishers []stream `json:"publishers"` 26 | Players []stream `json:"players"` 27 | } 28 | 29 | func NewServer(h av.Handler) *Server { 30 | return &Server{ 31 | handler: h, 32 | } 33 | } 34 | 35 | func (server *Server) Serve(l net.Listener) error { 36 | mux := http.NewServeMux() 37 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 38 | server.handleConn(w, r) 39 | }) 40 | mux.HandleFunc("/streams", func(w http.ResponseWriter, r *http.Request) { 41 | server.getStream(w, r) 42 | }) 43 | if err := http.Serve(l, mux); err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | // 获取发布和播放器的信息 50 | func (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *streams { 51 | rtmpStream := server.handler.(*rtmp.RtmpStream) 52 | if rtmpStream == nil { 53 | return nil 54 | } 55 | msgs := new(streams) 56 | 57 | rtmpStream.GetStreams().Range(func(key, val interface{}) bool { 58 | if s, ok := val.(*rtmp.Stream); ok { 59 | if s.GetReader() != nil { 60 | msg := stream{key.(string), s.GetReader().Info().UID} 61 | msgs.Publishers = append(msgs.Publishers, msg) 62 | } 63 | } 64 | return true 65 | }) 66 | 67 | rtmpStream.GetStreams().Range(func(key, val interface{}) bool { 68 | ws := val.(*rtmp.Stream).GetWs() 69 | 70 | ws.Range(func(k, v interface{}) bool { 71 | if pw, ok := v.(*rtmp.PackWriterCloser); ok { 72 | if pw.GetWriter() != nil { 73 | msg := stream{key.(string), pw.GetWriter().Info().UID} 74 | msgs.Players = append(msgs.Players, msg) 75 | } 76 | } 77 | return true 78 | }) 79 | return true 80 | }) 81 | 82 | return msgs 83 | } 84 | 85 | func (server *Server) getStream(w http.ResponseWriter, r *http.Request) { 86 | msgs := server.getStreams(w, r) 87 | if msgs == nil { 88 | return 89 | } 90 | resp, _ := json.Marshal(msgs) 91 | w.Header().Set("Content-Type", "application/json") 92 | w.Write(resp) 93 | } 94 | 95 | func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) { 96 | defer func() { 97 | if r := recover(); r != nil { 98 | log.Error("http flv handleConn panic: ", r) 99 | } 100 | }() 101 | 102 | url := r.URL.String() 103 | u := r.URL.Path 104 | if pos := strings.LastIndex(u, "."); pos < 0 || u[pos:] != ".flv" { 105 | http.Error(w, "invalid path", http.StatusBadRequest) 106 | return 107 | } 108 | path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv") 109 | paths := strings.SplitN(path, "/", 2) 110 | log.Debug("url:", u, "path:", path, "paths:", paths) 111 | 112 | if len(paths) != 2 { 113 | http.Error(w, "invalid path", http.StatusBadRequest) 114 | return 115 | } 116 | 117 | // 判断视屏流是否发布,如果没有发布,直接返回404 118 | msgs := server.getStreams(w, r) 119 | if msgs == nil || len(msgs.Publishers) == 0 { 120 | http.Error(w, "invalid path", http.StatusNotFound) 121 | return 122 | } else { 123 | include := false 124 | for _, item := range msgs.Publishers { 125 | if item.Key == path { 126 | include = true 127 | break 128 | } 129 | } 130 | if include == false { 131 | http.Error(w, "invalid path", http.StatusNotFound) 132 | return 133 | } 134 | } 135 | 136 | w.Header().Set("Access-Control-Allow-Origin", "*") 137 | writer := NewFLVWriter(paths[0], paths[1], url, w) 138 | 139 | server.handler.HandleWriter(writer) 140 | writer.Wait() 141 | } 142 | -------------------------------------------------------------------------------- /protocol/httpflv/writer.go: -------------------------------------------------------------------------------- 1 | package httpflv 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gwuhaolin/livego/av" 9 | "github.com/gwuhaolin/livego/protocol/amf" 10 | "github.com/gwuhaolin/livego/utils/pio" 11 | "github.com/gwuhaolin/livego/utils/uid" 12 | 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | const ( 17 | headerLen = 11 18 | maxQueueNum = 1024 19 | ) 20 | 21 | type FLVWriter struct { 22 | Uid string 23 | av.RWBaser 24 | app, title, url string 25 | buf []byte 26 | closed bool 27 | closedChan chan struct{} 28 | ctx http.ResponseWriter 29 | packetQueue chan *av.Packet 30 | } 31 | 32 | func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter { 33 | ret := &FLVWriter{ 34 | Uid: uid.NewId(), 35 | app: app, 36 | title: title, 37 | url: url, 38 | ctx: ctx, 39 | RWBaser: av.NewRWBaser(time.Second * 10), 40 | closedChan: make(chan struct{}), 41 | buf: make([]byte, headerLen), 42 | packetQueue: make(chan *av.Packet, maxQueueNum), 43 | } 44 | 45 | if _, err := ret.ctx.Write([]byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}); err != nil { 46 | log.Errorf("Error on response writer") 47 | ret.closed = true 48 | } 49 | pio.PutI32BE(ret.buf[:4], 0) 50 | if _, err := ret.ctx.Write(ret.buf[:4]); err != nil { 51 | log.Errorf("Error on response writer") 52 | ret.closed = true 53 | } 54 | go func() { 55 | err := ret.SendPacket() 56 | if err != nil { 57 | log.Debug("SendPacket error: ", err) 58 | ret.closed = true 59 | } 60 | 61 | }() 62 | return ret 63 | } 64 | 65 | func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) { 66 | log.Warningf("[%v] packet queue max!!!", info) 67 | for i := 0; i < maxQueueNum-84; i++ { 68 | tmpPkt, ok := <-pktQue 69 | if ok && tmpPkt.IsVideo { 70 | videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) 71 | // dont't drop sps config and dont't drop key frame 72 | if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { 73 | log.Debug("insert keyframe to queue") 74 | pktQue <- tmpPkt 75 | } 76 | 77 | if len(pktQue) > maxQueueNum-10 { 78 | <-pktQue 79 | } 80 | // drop other packet 81 | <-pktQue 82 | } 83 | // try to don't drop audio 84 | if ok && tmpPkt.IsAudio { 85 | log.Debug("insert audio to queue") 86 | pktQue <- tmpPkt 87 | } 88 | } 89 | log.Debug("packet queue len: ", len(pktQue)) 90 | } 91 | 92 | func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) { 93 | err = nil 94 | if flvWriter.closed { 95 | err = fmt.Errorf("flvwrite source closed") 96 | return 97 | } 98 | 99 | defer func() { 100 | if e := recover(); e != nil { 101 | err = fmt.Errorf("FLVWriter has already been closed:%v", e) 102 | } 103 | }() 104 | 105 | if len(flvWriter.packetQueue) >= maxQueueNum-24 { 106 | flvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info()) 107 | } else { 108 | flvWriter.packetQueue <- p 109 | } 110 | 111 | return 112 | } 113 | 114 | func (flvWriter *FLVWriter) SendPacket() error { 115 | for { 116 | p, ok := <-flvWriter.packetQueue 117 | if ok { 118 | flvWriter.RWBaser.SetPreTime() 119 | h := flvWriter.buf[:headerLen] 120 | typeID := av.TAG_VIDEO 121 | if !p.IsVideo { 122 | if p.IsMetadata { 123 | var err error 124 | typeID = av.TAG_SCRIPTDATAAMF0 125 | p.Data, err = amf.MetaDataReform(p.Data, amf.DEL) 126 | if err != nil { 127 | return err 128 | } 129 | } else { 130 | typeID = av.TAG_AUDIO 131 | } 132 | } 133 | dataLen := len(p.Data) 134 | timestamp := p.TimeStamp 135 | timestamp += flvWriter.BaseTimeStamp() 136 | flvWriter.RWBaser.RecTimeStamp(timestamp, uint32(typeID)) 137 | 138 | preDataLen := dataLen + headerLen 139 | timestampbase := timestamp & 0xffffff 140 | timestampExt := timestamp >> 24 & 0xff 141 | 142 | pio.PutU8(h[0:1], uint8(typeID)) 143 | pio.PutI24BE(h[1:4], int32(dataLen)) 144 | pio.PutI24BE(h[4:7], int32(timestampbase)) 145 | pio.PutU8(h[7:8], uint8(timestampExt)) 146 | 147 | if _, err := flvWriter.ctx.Write(h); err != nil { 148 | return err 149 | } 150 | 151 | if _, err := flvWriter.ctx.Write(p.Data); err != nil { 152 | return err 153 | } 154 | 155 | pio.PutI32BE(h[:4], int32(preDataLen)) 156 | if _, err := flvWriter.ctx.Write(h[:4]); err != nil { 157 | return err 158 | } 159 | } else { 160 | return fmt.Errorf("closed") 161 | } 162 | 163 | } 164 | } 165 | 166 | func (flvWriter *FLVWriter) Wait() { 167 | select { 168 | case <-flvWriter.closedChan: 169 | return 170 | } 171 | } 172 | 173 | func (flvWriter *FLVWriter) Close(error) { 174 | log.Debug("http flv closed") 175 | if !flvWriter.closed { 176 | close(flvWriter.packetQueue) 177 | close(flvWriter.closedChan) 178 | } 179 | flvWriter.closed = true 180 | } 181 | 182 | func (flvWriter *FLVWriter) Info() (ret av.Info) { 183 | ret.UID = flvWriter.Uid 184 | ret.URL = flvWriter.url 185 | ret.Key = flvWriter.app + "/" + flvWriter.title 186 | ret.Inter = true 187 | return 188 | } 189 | -------------------------------------------------------------------------------- /protocol/rtmp/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/gwuhaolin/livego/av" 5 | "github.com/gwuhaolin/livego/configure" 6 | ) 7 | 8 | type Cache struct { 9 | gop *GopCache 10 | videoSeq *SpecialCache 11 | audioSeq *SpecialCache 12 | metadata *SpecialCache 13 | } 14 | 15 | func NewCache() *Cache { 16 | return &Cache{ 17 | gop: NewGopCache(configure.Config.GetInt("gop_num")), 18 | videoSeq: NewSpecialCache(), 19 | audioSeq: NewSpecialCache(), 20 | metadata: NewSpecialCache(), 21 | } 22 | } 23 | 24 | func (cache *Cache) Write(p av.Packet) { 25 | if p.IsMetadata { 26 | cache.metadata.Write(&p) 27 | return 28 | } else { 29 | if !p.IsVideo { 30 | ah, ok := p.Header.(av.AudioPacketHeader) 31 | if ok { 32 | if ah.SoundFormat() == av.SOUND_AAC && 33 | ah.AACPacketType() == av.AAC_SEQHDR { 34 | cache.audioSeq.Write(&p) 35 | return 36 | } else { 37 | return 38 | } 39 | } 40 | 41 | } else { 42 | vh, ok := p.Header.(av.VideoPacketHeader) 43 | if ok { 44 | if vh.IsSeq() { 45 | cache.videoSeq.Write(&p) 46 | return 47 | } 48 | } else { 49 | return 50 | } 51 | 52 | } 53 | } 54 | cache.gop.Write(&p) 55 | } 56 | 57 | func (cache *Cache) Send(w av.WriteCloser) error { 58 | if err := cache.metadata.Send(w); err != nil { 59 | return err 60 | } 61 | 62 | if err := cache.videoSeq.Send(w); err != nil { 63 | return err 64 | } 65 | 66 | if err := cache.audioSeq.Send(w); err != nil { 67 | return err 68 | } 69 | 70 | if err := cache.gop.Send(w); err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /protocol/rtmp/cache/gop.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | ) 8 | 9 | var ( 10 | maxGOPCap int = 1024 11 | ErrGopTooBig = fmt.Errorf("gop to big") 12 | ) 13 | 14 | type array struct { 15 | index int 16 | packets []*av.Packet 17 | } 18 | 19 | func newArray() *array { 20 | ret := &array{ 21 | index: 0, 22 | packets: make([]*av.Packet, 0, maxGOPCap), 23 | } 24 | return ret 25 | } 26 | 27 | func (array *array) reset() { 28 | array.index = 0 29 | array.packets = array.packets[:0] 30 | } 31 | 32 | func (array *array) write(packet *av.Packet) error { 33 | if array.index >= maxGOPCap { 34 | return ErrGopTooBig 35 | } 36 | array.packets = append(array.packets, packet) 37 | array.index++ 38 | return nil 39 | } 40 | 41 | func (array *array) send(w av.WriteCloser) error { 42 | var err error 43 | for i := 0; i < array.index; i++ { 44 | packet := array.packets[i] 45 | if err = w.Write(packet); err != nil { 46 | return err 47 | } 48 | } 49 | return err 50 | } 51 | 52 | type GopCache struct { 53 | start bool 54 | num int 55 | count int 56 | nextindex int 57 | gops []*array 58 | } 59 | 60 | func NewGopCache(num int) *GopCache { 61 | return &GopCache{ 62 | count: num, 63 | gops: make([]*array, num), 64 | } 65 | } 66 | 67 | func (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool) error { 68 | var ginc *array 69 | if startNew { 70 | ginc = gopCache.gops[gopCache.nextindex] 71 | if ginc == nil { 72 | ginc = newArray() 73 | gopCache.num++ 74 | gopCache.gops[gopCache.nextindex] = ginc 75 | } else { 76 | ginc.reset() 77 | } 78 | gopCache.nextindex = (gopCache.nextindex + 1) % gopCache.count 79 | } else { 80 | ginc = gopCache.gops[(gopCache.nextindex+1)%gopCache.count] 81 | } 82 | ginc.write(chunk) 83 | 84 | return nil 85 | } 86 | 87 | func (gopCache *GopCache) Write(p *av.Packet) { 88 | var ok bool 89 | if p.IsVideo { 90 | vh := p.Header.(av.VideoPacketHeader) 91 | if vh.IsKeyFrame() && !vh.IsSeq() { 92 | ok = true 93 | } 94 | } 95 | if ok || gopCache.start { 96 | gopCache.start = true 97 | gopCache.writeToArray(p, ok) 98 | } 99 | } 100 | 101 | func (gopCache *GopCache) sendTo(w av.WriteCloser) error { 102 | var err error 103 | pos := (gopCache.nextindex + 1) % gopCache.count 104 | for i := 0; i < gopCache.num; i++ { 105 | index := (pos - gopCache.num + 1) + i 106 | if index < 0 { 107 | index += gopCache.count 108 | } 109 | g := gopCache.gops[index] 110 | err = g.send(w) 111 | if err != nil { 112 | return err 113 | } 114 | } 115 | return nil 116 | } 117 | 118 | func (gopCache *GopCache) Send(w av.WriteCloser) error { 119 | return gopCache.sendTo(w) 120 | } 121 | -------------------------------------------------------------------------------- /protocol/rtmp/cache/special.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | "github.com/gwuhaolin/livego/protocol/amf" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | const ( 13 | SetDataFrame string = "@setDataFrame" 14 | OnMetaData string = "onMetaData" 15 | ) 16 | 17 | var setFrameFrame []byte 18 | 19 | func init() { 20 | b := bytes.NewBuffer(nil) 21 | encoder := &amf.Encoder{} 22 | if _, err := encoder.Encode(b, SetDataFrame, amf.AMF0); err != nil { 23 | log.Fatal(err) 24 | } 25 | setFrameFrame = b.Bytes() 26 | } 27 | 28 | type SpecialCache struct { 29 | full bool 30 | p *av.Packet 31 | } 32 | 33 | func NewSpecialCache() *SpecialCache { 34 | return &SpecialCache{} 35 | } 36 | 37 | func (specialCache *SpecialCache) Write(p *av.Packet) { 38 | specialCache.p = p 39 | specialCache.full = true 40 | } 41 | 42 | func (specialCache *SpecialCache) Send(w av.WriteCloser) error { 43 | if !specialCache.full { 44 | return nil 45 | } 46 | 47 | // demux in hls will change p.Data, only send a copy here 48 | newPacket := *specialCache.p 49 | return w.Write(&newPacket) 50 | } 51 | -------------------------------------------------------------------------------- /protocol/rtmp/core/chunk_stream.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/gwuhaolin/livego/av" 8 | "github.com/gwuhaolin/livego/utils/pool" 9 | ) 10 | 11 | type ChunkStream struct { 12 | Format uint32 13 | CSID uint32 14 | Timestamp uint32 15 | Length uint32 16 | TypeID uint32 17 | StreamID uint32 18 | timeDelta uint32 19 | exted bool 20 | index uint32 21 | remain uint32 22 | got bool 23 | tmpFromat uint32 24 | Data []byte 25 | } 26 | 27 | func (chunkStream *ChunkStream) full() bool { 28 | return chunkStream.got 29 | } 30 | 31 | func (chunkStream *ChunkStream) new(pool *pool.Pool) { 32 | chunkStream.got = false 33 | chunkStream.index = 0 34 | chunkStream.remain = chunkStream.Length 35 | chunkStream.Data = pool.Get(int(chunkStream.Length)) 36 | } 37 | 38 | func (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error { 39 | //Chunk Basic Header 40 | h := chunkStream.Format << 6 41 | switch { 42 | case chunkStream.CSID < 64: 43 | h |= chunkStream.CSID 44 | w.WriteUintBE(h, 1) 45 | case chunkStream.CSID-64 < 256: 46 | h |= 0 47 | w.WriteUintBE(h, 1) 48 | w.WriteUintLE(chunkStream.CSID-64, 1) 49 | case chunkStream.CSID-64 < 65536: 50 | h |= 1 51 | w.WriteUintBE(h, 1) 52 | w.WriteUintLE(chunkStream.CSID-64, 2) 53 | } 54 | //Chunk Message Header 55 | ts := chunkStream.Timestamp 56 | if chunkStream.Format == 3 { 57 | goto END 58 | } 59 | if chunkStream.Timestamp > 0xffffff { 60 | ts = 0xffffff 61 | } 62 | w.WriteUintBE(ts, 3) 63 | if chunkStream.Format == 2 { 64 | goto END 65 | } 66 | if chunkStream.Length > 0xffffff { 67 | return fmt.Errorf("length=%d", chunkStream.Length) 68 | } 69 | w.WriteUintBE(chunkStream.Length, 3) 70 | w.WriteUintBE(chunkStream.TypeID, 1) 71 | if chunkStream.Format == 1 { 72 | goto END 73 | } 74 | w.WriteUintLE(chunkStream.StreamID, 4) 75 | END: 76 | //Extended Timestamp 77 | if ts >= 0xffffff { 78 | w.WriteUintBE(chunkStream.Timestamp, 4) 79 | } 80 | return w.WriteError() 81 | } 82 | 83 | func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize int) error { 84 | if chunkStream.TypeID == av.TAG_AUDIO { 85 | chunkStream.CSID = 4 86 | } else if chunkStream.TypeID == av.TAG_VIDEO || 87 | chunkStream.TypeID == av.TAG_SCRIPTDATAAMF0 || 88 | chunkStream.TypeID == av.TAG_SCRIPTDATAAMF3 { 89 | chunkStream.CSID = 6 90 | } 91 | 92 | totalLen := uint32(0) 93 | numChunks := (chunkStream.Length / uint32(chunkSize)) 94 | for i := uint32(0); i <= numChunks; i++ { 95 | if totalLen == chunkStream.Length { 96 | break 97 | } 98 | if i == 0 { 99 | chunkStream.Format = uint32(0) 100 | } else { 101 | chunkStream.Format = uint32(3) 102 | } 103 | if err := chunkStream.writeHeader(w); err != nil { 104 | return err 105 | } 106 | inc := uint32(chunkSize) 107 | start := uint32(i) * uint32(chunkSize) 108 | if uint32(len(chunkStream.Data))-start <= inc { 109 | inc = uint32(len(chunkStream.Data)) - start 110 | } 111 | totalLen += inc 112 | end := start + inc 113 | buf := chunkStream.Data[start:end] 114 | if _, err := w.Write(buf); err != nil { 115 | return err 116 | } 117 | } 118 | 119 | return nil 120 | 121 | } 122 | 123 | func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error { 124 | if chunkStream.remain != 0 && chunkStream.tmpFromat != 3 { 125 | return fmt.Errorf("invalid remain = %d", chunkStream.remain) 126 | } 127 | switch chunkStream.CSID { 128 | case 0: 129 | id, _ := r.ReadUintLE(1) 130 | chunkStream.CSID = id + 64 131 | case 1: 132 | id, _ := r.ReadUintLE(2) 133 | chunkStream.CSID = id + 64 134 | } 135 | 136 | switch chunkStream.tmpFromat { 137 | case 0: 138 | chunkStream.Format = chunkStream.tmpFromat 139 | chunkStream.Timestamp, _ = r.ReadUintBE(3) 140 | chunkStream.Length, _ = r.ReadUintBE(3) 141 | chunkStream.TypeID, _ = r.ReadUintBE(1) 142 | chunkStream.StreamID, _ = r.ReadUintLE(4) 143 | if chunkStream.Timestamp == 0xffffff { 144 | chunkStream.Timestamp, _ = r.ReadUintBE(4) 145 | chunkStream.exted = true 146 | } else { 147 | chunkStream.exted = false 148 | } 149 | chunkStream.new(pool) 150 | case 1: 151 | chunkStream.Format = chunkStream.tmpFromat 152 | timeStamp, _ := r.ReadUintBE(3) 153 | chunkStream.Length, _ = r.ReadUintBE(3) 154 | chunkStream.TypeID, _ = r.ReadUintBE(1) 155 | if timeStamp == 0xffffff { 156 | timeStamp, _ = r.ReadUintBE(4) 157 | chunkStream.exted = true 158 | } else { 159 | chunkStream.exted = false 160 | } 161 | chunkStream.timeDelta = timeStamp 162 | chunkStream.Timestamp += timeStamp 163 | chunkStream.new(pool) 164 | case 2: 165 | chunkStream.Format = chunkStream.tmpFromat 166 | timeStamp, _ := r.ReadUintBE(3) 167 | if timeStamp == 0xffffff { 168 | timeStamp, _ = r.ReadUintBE(4) 169 | chunkStream.exted = true 170 | } else { 171 | chunkStream.exted = false 172 | } 173 | chunkStream.timeDelta = timeStamp 174 | chunkStream.Timestamp += timeStamp 175 | chunkStream.new(pool) 176 | case 3: 177 | if chunkStream.remain == 0 { 178 | switch chunkStream.Format { 179 | case 0: 180 | if chunkStream.exted { 181 | timestamp, _ := r.ReadUintBE(4) 182 | chunkStream.Timestamp = timestamp 183 | } 184 | case 1, 2: 185 | var timedet uint32 186 | if chunkStream.exted { 187 | timedet, _ = r.ReadUintBE(4) 188 | } else { 189 | timedet = chunkStream.timeDelta 190 | } 191 | chunkStream.Timestamp += timedet 192 | } 193 | chunkStream.new(pool) 194 | } else { 195 | if chunkStream.exted { 196 | b, err := r.Peek(4) 197 | if err != nil { 198 | return err 199 | } 200 | tmpts := binary.BigEndian.Uint32(b) 201 | if tmpts == chunkStream.Timestamp { 202 | r.Discard(4) 203 | } 204 | } 205 | } 206 | default: 207 | return fmt.Errorf("invalid format=%d", chunkStream.Format) 208 | } 209 | size := int(chunkStream.remain) 210 | if size > int(chunkSize) { 211 | size = int(chunkSize) 212 | } 213 | 214 | buf := chunkStream.Data[chunkStream.index : chunkStream.index+uint32(size)] 215 | if _, err := r.Read(buf); err != nil { 216 | return err 217 | } 218 | chunkStream.index += uint32(size) 219 | chunkStream.remain -= uint32(size) 220 | if chunkStream.remain == 0 { 221 | chunkStream.got = true 222 | } 223 | 224 | return r.readError 225 | } 226 | -------------------------------------------------------------------------------- /protocol/rtmp/core/chunk_stream_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/gwuhaolin/livego/utils/pool" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestChunkRead1(t *testing.T) { 13 | at := assert.New(t) 14 | data := []byte{ 15 | 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 16 | } 17 | data1 := make([]byte, 128) 18 | data2 := make([]byte, 51) 19 | data = append(data, data1...) 20 | data = append(data, 0xc6) 21 | data = append(data, data1...) 22 | data = append(data, 0xc6) 23 | data = append(data, data2...) 24 | 25 | rw := NewReadWriter(bytes.NewBuffer(data), 1024) 26 | chunkinc := &ChunkStream{} 27 | 28 | for { 29 | h, _ := rw.ReadUintBE(1) 30 | chunkinc.tmpFromat = h >> 6 31 | chunkinc.CSID = h & 0x3f 32 | chunkinc.readChunk(rw, 128, pool.NewPool()) 33 | if chunkinc.remain == 0 { 34 | break 35 | } 36 | } 37 | 38 | at.Equal(int(chunkinc.Length), 307) 39 | at.Equal(int(chunkinc.TypeID), 9) 40 | at.Equal(int(chunkinc.StreamID), 1) 41 | at.Equal(len(chunkinc.Data), 307) 42 | at.Equal(int(chunkinc.remain), 0) 43 | 44 | data = []byte{ 45 | 0x06, 0xff, 0xff, 0xff, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 46 | } 47 | data = append(data, data1...) 48 | data = append(data, 0xc6) 49 | data = append(data, []byte{0x00, 0x00, 0x00, 0x05}...) 50 | data = append(data, data1...) 51 | data = append(data, 0xc6) 52 | data = append(data, data2...) 53 | 54 | rw = NewReadWriter(bytes.NewBuffer(data), 1024) 55 | chunkinc = &ChunkStream{} 56 | 57 | h, _ := rw.ReadUintBE(1) 58 | chunkinc.tmpFromat = h >> 6 59 | chunkinc.CSID = h & 0x3f 60 | chunkinc.readChunk(rw, 128, pool.NewPool()) 61 | 62 | h, _ = rw.ReadUintBE(1) 63 | chunkinc.tmpFromat = h >> 6 64 | chunkinc.CSID = h & 0x3f 65 | chunkinc.readChunk(rw, 128, pool.NewPool()) 66 | 67 | h, _ = rw.ReadUintBE(1) 68 | chunkinc.tmpFromat = h >> 6 69 | chunkinc.CSID = h & 0x3f 70 | chunkinc.readChunk(rw, 128, pool.NewPool()) 71 | 72 | at.Equal(int(chunkinc.Length), 307) 73 | at.Equal(int(chunkinc.TypeID), 9) 74 | at.Equal(int(chunkinc.StreamID), 1) 75 | at.Equal(len(chunkinc.Data), 307) 76 | at.Equal(chunkinc.exted, true) 77 | at.Equal(int(chunkinc.Timestamp), 5) 78 | at.Equal(int(chunkinc.remain), 0) 79 | 80 | } 81 | 82 | func TestWriteChunk(t *testing.T) { 83 | at := assert.New(t) 84 | chunkinc := &ChunkStream{} 85 | 86 | chunkinc.Length = 307 87 | chunkinc.TypeID = 9 88 | chunkinc.CSID = 4 89 | chunkinc.Timestamp = 40 90 | chunkinc.Data = make([]byte, 307) 91 | 92 | bf := bytes.NewBuffer(nil) 93 | w := NewReadWriter(bf, 1024) 94 | err := chunkinc.writeChunk(w, 128) 95 | w.Flush() 96 | at.Equal(err, nil) 97 | at.Equal(len(bf.Bytes()), 321) 98 | } 99 | -------------------------------------------------------------------------------- /protocol/rtmp/core/conn.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "time" 7 | 8 | "github.com/gwuhaolin/livego/utils/pio" 9 | "github.com/gwuhaolin/livego/utils/pool" 10 | ) 11 | 12 | const ( 13 | _ = iota 14 | idSetChunkSize 15 | idAbortMessage 16 | idAck 17 | idUserControlMessages 18 | idWindowAckSize 19 | idSetPeerBandwidth 20 | ) 21 | 22 | type Conn struct { 23 | net.Conn 24 | chunkSize uint32 25 | remoteChunkSize uint32 26 | windowAckSize uint32 27 | remoteWindowAckSize uint32 28 | received uint32 29 | ackReceived uint32 30 | rw *ReadWriter 31 | pool *pool.Pool 32 | chunks map[uint32]ChunkStream 33 | } 34 | 35 | func NewConn(c net.Conn, bufferSize int) *Conn { 36 | return &Conn{ 37 | Conn: c, 38 | chunkSize: 128, 39 | remoteChunkSize: 128, 40 | windowAckSize: 2500000, 41 | remoteWindowAckSize: 2500000, 42 | pool: pool.NewPool(), 43 | rw: NewReadWriter(c, bufferSize), 44 | chunks: make(map[uint32]ChunkStream), 45 | } 46 | } 47 | 48 | func (conn *Conn) Read(c *ChunkStream) error { 49 | for { 50 | h, _ := conn.rw.ReadUintBE(1) 51 | // if err != nil { 52 | // log.Println("read from conn error: ", err) 53 | // return err 54 | // } 55 | format := h >> 6 56 | csid := h & 0x3f 57 | cs, ok := conn.chunks[csid] 58 | if !ok { 59 | cs = ChunkStream{} 60 | conn.chunks[csid] = cs 61 | } 62 | cs.tmpFromat = format 63 | cs.CSID = csid 64 | err := cs.readChunk(conn.rw, conn.remoteChunkSize, conn.pool) 65 | if err != nil { 66 | return err 67 | } 68 | conn.chunks[csid] = cs 69 | if cs.full() { 70 | *c = cs 71 | break 72 | } 73 | } 74 | 75 | conn.handleControlMsg(c) 76 | 77 | conn.ack(c.Length) 78 | 79 | return nil 80 | } 81 | 82 | func (conn *Conn) Write(c *ChunkStream) error { 83 | if c.TypeID == idSetChunkSize { 84 | conn.chunkSize = binary.BigEndian.Uint32(c.Data) 85 | } 86 | return c.writeChunk(conn.rw, int(conn.chunkSize)) 87 | } 88 | 89 | func (conn *Conn) Flush() error { 90 | return conn.rw.Flush() 91 | } 92 | 93 | func (conn *Conn) Close() error { 94 | return conn.Conn.Close() 95 | } 96 | 97 | func (conn *Conn) RemoteAddr() net.Addr { 98 | return conn.Conn.RemoteAddr() 99 | } 100 | 101 | func (conn *Conn) LocalAddr() net.Addr { 102 | return conn.Conn.LocalAddr() 103 | } 104 | 105 | func (conn *Conn) SetDeadline(t time.Time) error { 106 | return conn.Conn.SetDeadline(t) 107 | } 108 | 109 | func (conn *Conn) NewAck(size uint32) ChunkStream { 110 | return initControlMsg(idAck, 4, size) 111 | } 112 | 113 | func (conn *Conn) NewSetChunkSize(size uint32) ChunkStream { 114 | return initControlMsg(idSetChunkSize, 4, size) 115 | } 116 | 117 | func (conn *Conn) NewWindowAckSize(size uint32) ChunkStream { 118 | return initControlMsg(idWindowAckSize, 4, size) 119 | } 120 | 121 | func (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream { 122 | ret := initControlMsg(idSetPeerBandwidth, 5, size) 123 | ret.Data[4] = 2 124 | return ret 125 | } 126 | 127 | func (conn *Conn) handleControlMsg(c *ChunkStream) { 128 | if c.TypeID == idSetChunkSize { 129 | conn.remoteChunkSize = binary.BigEndian.Uint32(c.Data) 130 | } else if c.TypeID == idWindowAckSize { 131 | conn.remoteWindowAckSize = binary.BigEndian.Uint32(c.Data) 132 | } 133 | } 134 | 135 | func (conn *Conn) ack(size uint32) { 136 | conn.received += uint32(size) 137 | conn.ackReceived += uint32(size) 138 | if conn.received >= 0xf0000000 { 139 | conn.received = 0 140 | } 141 | if conn.ackReceived >= conn.remoteWindowAckSize { 142 | cs := conn.NewAck(conn.ackReceived) 143 | cs.writeChunk(conn.rw, int(conn.chunkSize)) 144 | conn.ackReceived = 0 145 | } 146 | } 147 | 148 | func initControlMsg(id, size, value uint32) ChunkStream { 149 | ret := ChunkStream{ 150 | Format: 0, 151 | CSID: 2, 152 | TypeID: id, 153 | StreamID: 0, 154 | Length: size, 155 | Data: make([]byte, size), 156 | } 157 | pio.PutU32BE(ret.Data[:size], value) 158 | return ret 159 | } 160 | 161 | const ( 162 | streamBegin uint32 = 0 163 | streamEOF uint32 = 1 164 | streamDry uint32 = 2 165 | setBufferLen uint32 = 3 166 | streamIsRecorded uint32 = 4 167 | pingRequest uint32 = 6 168 | pingResponse uint32 = 7 169 | ) 170 | 171 | /* 172 | +------------------------------+------------------------- 173 | | Event Type ( 2- bytes ) | Event Data 174 | +------------------------------+------------------------- 175 | Pay load for the ‘User Control Message’. 176 | */ 177 | func (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream { 178 | var ret ChunkStream 179 | buflen += 2 180 | ret = ChunkStream{ 181 | Format: 0, 182 | CSID: 2, 183 | TypeID: 4, 184 | StreamID: 1, 185 | Length: buflen, 186 | Data: make([]byte, buflen), 187 | } 188 | ret.Data[0] = byte(eventType >> 8 & 0xff) 189 | ret.Data[1] = byte(eventType & 0xff) 190 | return ret 191 | } 192 | 193 | func (conn *Conn) SetBegin() { 194 | ret := conn.userControlMsg(streamBegin, 4) 195 | for i := 0; i < 4; i++ { 196 | ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff) 197 | } 198 | conn.Write(&ret) 199 | } 200 | 201 | func (conn *Conn) SetRecorded() { 202 | ret := conn.userControlMsg(streamIsRecorded, 4) 203 | for i := 0; i < 4; i++ { 204 | ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff) 205 | } 206 | conn.Write(&ret) 207 | } 208 | -------------------------------------------------------------------------------- /protocol/rtmp/core/conn_client.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | "io" 9 | "math/rand" 10 | "net" 11 | neturl "net/url" 12 | "strings" 13 | 14 | "github.com/gwuhaolin/livego/av" 15 | "github.com/gwuhaolin/livego/configure" 16 | "github.com/gwuhaolin/livego/protocol/amf" 17 | 18 | log "github.com/sirupsen/logrus" 19 | ) 20 | 21 | var ( 22 | respResult = "_result" 23 | respError = "_error" 24 | onStatus = "onStatus" 25 | publishStart = "NetStream.Publish.Start" 26 | playStart = "NetStream.Play.Start" 27 | connectSuccess = "NetConnection.Connect.Success" 28 | onBWDone = "onBWDone" 29 | ) 30 | 31 | var ( 32 | ErrFail = fmt.Errorf("response err") 33 | ) 34 | 35 | type ConnClient struct { 36 | done bool 37 | transID int 38 | url string 39 | tcurl string 40 | app string 41 | title string 42 | curcmdName string 43 | streamid uint32 44 | isRTMPS bool 45 | conn *Conn 46 | encoder *amf.Encoder 47 | decoder *amf.Decoder 48 | bytesw *bytes.Buffer 49 | } 50 | 51 | func NewConnClient() *ConnClient { 52 | return &ConnClient{ 53 | transID: 1, 54 | bytesw: bytes.NewBuffer(nil), 55 | encoder: &amf.Encoder{}, 56 | decoder: &amf.Decoder{}, 57 | } 58 | } 59 | 60 | func (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version) (ret []interface{}, err error) { 61 | vs, err := connClient.decoder.DecodeBatch(r, ver) 62 | return vs, err 63 | } 64 | 65 | func (connClient *ConnClient) readRespMsg() error { 66 | var err error 67 | var rc ChunkStream 68 | for { 69 | if err = connClient.conn.Read(&rc); err != nil { 70 | return err 71 | } 72 | if err != nil && err != io.EOF { 73 | return err 74 | } 75 | switch rc.TypeID { 76 | case 20, 17: 77 | r := bytes.NewReader(rc.Data) 78 | vs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0) 79 | 80 | log.Debugf("readRespMsg: vs=%v", vs) 81 | for k, v := range vs { 82 | switch v.(type) { 83 | case string: 84 | switch connClient.curcmdName { 85 | case cmdConnect, cmdCreateStream: 86 | if v.(string) != respResult { 87 | return fmt.Errorf(v.(string)) 88 | } 89 | 90 | case cmdPublish: 91 | if v.(string) != onStatus { 92 | return ErrFail 93 | } 94 | } 95 | case float64: 96 | switch connClient.curcmdName { 97 | case cmdConnect, cmdCreateStream: 98 | id := int(v.(float64)) 99 | 100 | if k == 1 { 101 | if id != connClient.transID { 102 | return ErrFail 103 | } 104 | } else if k == 3 { 105 | connClient.streamid = uint32(id) 106 | } 107 | case cmdPublish: 108 | if int(v.(float64)) != 0 { 109 | return ErrFail 110 | } 111 | } 112 | case amf.Object: 113 | objmap := v.(amf.Object) 114 | switch connClient.curcmdName { 115 | case cmdConnect: 116 | code, ok := objmap["code"] 117 | if ok && code.(string) != connectSuccess { 118 | return ErrFail 119 | } 120 | case cmdPublish: 121 | code, ok := objmap["code"] 122 | if ok && code.(string) != publishStart { 123 | return ErrFail 124 | } 125 | } 126 | } 127 | } 128 | 129 | return nil 130 | } 131 | } 132 | } 133 | 134 | func (connClient *ConnClient) writeMsg(args ...interface{}) error { 135 | connClient.bytesw.Reset() 136 | for _, v := range args { 137 | if _, err := connClient.encoder.Encode(connClient.bytesw, v, amf.AMF0); err != nil { 138 | return err 139 | } 140 | } 141 | msg := connClient.bytesw.Bytes() 142 | c := ChunkStream{ 143 | Format: 0, 144 | CSID: 3, 145 | Timestamp: 0, 146 | TypeID: 20, 147 | StreamID: connClient.streamid, 148 | Length: uint32(len(msg)), 149 | Data: msg, 150 | } 151 | connClient.conn.Write(&c) 152 | return connClient.conn.Flush() 153 | } 154 | 155 | func (connClient *ConnClient) writeConnectMsg() error { 156 | event := make(amf.Object) 157 | event["app"] = connClient.app 158 | event["type"] = "nonprivate" 159 | event["flashVer"] = "FMS.3.1" 160 | event["tcUrl"] = connClient.tcurl 161 | connClient.curcmdName = cmdConnect 162 | 163 | log.Debugf("writeConnectMsg: connClient.transID=%d, event=%v", connClient.transID, event) 164 | if err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil { 165 | return err 166 | } 167 | return connClient.readRespMsg() 168 | } 169 | 170 | func (connClient *ConnClient) writeCreateStreamMsg() error { 171 | connClient.transID++ 172 | connClient.curcmdName = cmdCreateStream 173 | 174 | log.Debugf("writeCreateStreamMsg: connClient.transID=%d", connClient.transID) 175 | if err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil { 176 | return err 177 | } 178 | 179 | for { 180 | err := connClient.readRespMsg() 181 | if err == nil { 182 | return err 183 | } 184 | 185 | if err == ErrFail { 186 | log.Debugf("writeCreateStreamMsg readRespMsg err=%v", err) 187 | return err 188 | } 189 | } 190 | 191 | } 192 | 193 | func (connClient *ConnClient) writePublishMsg() error { 194 | connClient.transID++ 195 | connClient.curcmdName = cmdPublish 196 | if err := connClient.writeMsg(cmdPublish, connClient.transID, nil, connClient.title, publishLive); err != nil { 197 | return err 198 | } 199 | return connClient.readRespMsg() 200 | } 201 | 202 | func (connClient *ConnClient) writePlayMsg() error { 203 | connClient.transID++ 204 | connClient.curcmdName = cmdPlay 205 | log.Debugf("writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v", 206 | connClient.transID, cmdPlay, connClient.title) 207 | 208 | if err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil { 209 | return err 210 | } 211 | return connClient.readRespMsg() 212 | } 213 | 214 | func (connClient *ConnClient) Start(url string, method string) error { 215 | u, err := neturl.Parse(url) 216 | if err != nil { 217 | return err 218 | } 219 | connClient.url = url 220 | path := strings.TrimLeft(u.Path, "/") 221 | ps := strings.SplitN(path, "/", 2) 222 | if len(ps) != 2 { 223 | return fmt.Errorf("u path err: %s", path) 224 | } 225 | connClient.app = ps[0] 226 | connClient.title = ps[1] 227 | if u.RawQuery != "" { 228 | connClient.title += "?" + u.RawQuery 229 | } 230 | connClient.isRTMPS = strings.HasPrefix(url, "rtmps://") 231 | 232 | var port string 233 | if connClient.isRTMPS { 234 | connClient.tcurl = "rtmps://" + u.Host + "/" + connClient.app 235 | port = ":443" 236 | } else { 237 | connClient.tcurl = "rtmp://" + u.Host + "/" + connClient.app 238 | port = ":1935" 239 | } 240 | 241 | host := u.Host 242 | localIP := ":0" 243 | var remoteIP string 244 | if strings.Index(host, ":") != -1 { 245 | host, port, err = net.SplitHostPort(host) 246 | if err != nil { 247 | return err 248 | } 249 | port = ":" + port 250 | } 251 | ips, err := net.LookupIP(host) 252 | log.Debugf("ips: %v, host: %v", ips, host) 253 | if err != nil { 254 | log.Warning(err) 255 | return err 256 | } 257 | remoteIP = ips[rand.Intn(len(ips))].String() 258 | if strings.Index(remoteIP, ":") == -1 { 259 | remoteIP += port 260 | } 261 | 262 | local, err := net.ResolveTCPAddr("tcp", localIP) 263 | if err != nil { 264 | log.Warning(err) 265 | return err 266 | } 267 | log.Debug("remoteIP: ", remoteIP) 268 | remote, err := net.ResolveTCPAddr("tcp", remoteIP) 269 | if err != nil { 270 | log.Warning(err) 271 | return err 272 | } 273 | 274 | var conn net.Conn 275 | if connClient.isRTMPS { 276 | var config tls.Config 277 | if configure.Config.GetBool("enable_tls_verify") { 278 | roots, err := x509.SystemCertPool() 279 | if err != nil { 280 | log.Warning(err) 281 | return err 282 | } 283 | config.RootCAs = roots 284 | } else { 285 | config.InsecureSkipVerify = true 286 | } 287 | 288 | conn, err = tls.Dial("tcp", remoteIP, &config) 289 | if err != nil { 290 | log.Warning(err) 291 | return err 292 | } 293 | } else { 294 | conn, err = net.DialTCP("tcp", local, remote) 295 | if err != nil { 296 | log.Warning(err) 297 | return err 298 | } 299 | } 300 | 301 | log.Debug("connection:", "local:", conn.LocalAddr(), "remote:", conn.RemoteAddr()) 302 | 303 | connClient.conn = NewConn(conn, 4*1024) 304 | 305 | log.Debug("HandshakeClient....") 306 | if err := connClient.conn.HandshakeClient(); err != nil { 307 | return err 308 | } 309 | 310 | log.Debug("writeConnectMsg....") 311 | if err := connClient.writeConnectMsg(); err != nil { 312 | return err 313 | } 314 | log.Debug("writeCreateStreamMsg....") 315 | if err := connClient.writeCreateStreamMsg(); err != nil { 316 | log.Debug("writeCreateStreamMsg error", err) 317 | return err 318 | } 319 | 320 | log.Debug("method control:", method, av.PUBLISH, av.PLAY) 321 | if method == av.PUBLISH { 322 | if err := connClient.writePublishMsg(); err != nil { 323 | return err 324 | } 325 | } else if method == av.PLAY { 326 | if err := connClient.writePlayMsg(); err != nil { 327 | return err 328 | } 329 | } 330 | 331 | return nil 332 | } 333 | 334 | func (connClient *ConnClient) Write(c ChunkStream) error { 335 | if c.TypeID == av.TAG_SCRIPTDATAAMF0 || 336 | c.TypeID == av.TAG_SCRIPTDATAAMF3 { 337 | var err error 338 | if c.Data, err = amf.MetaDataReform(c.Data, amf.ADD); err != nil { 339 | return err 340 | } 341 | c.Length = uint32(len(c.Data)) 342 | } 343 | return connClient.conn.Write(&c) 344 | } 345 | 346 | func (connClient *ConnClient) Flush() error { 347 | return connClient.conn.Flush() 348 | } 349 | 350 | func (connClient *ConnClient) Read(c *ChunkStream) (err error) { 351 | return connClient.conn.Read(c) 352 | } 353 | 354 | func (connClient *ConnClient) GetInfo() (app string, name string, url string) { 355 | app = connClient.app 356 | name = connClient.title 357 | url = connClient.url 358 | return 359 | } 360 | 361 | func (connClient *ConnClient) GetStreamId() uint32 { 362 | return connClient.streamid 363 | } 364 | 365 | func (connClient *ConnClient) Close(err error) { 366 | connClient.conn.Close() 367 | } 368 | -------------------------------------------------------------------------------- /protocol/rtmp/core/conn_server.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/gwuhaolin/livego/av" 9 | "github.com/gwuhaolin/livego/protocol/amf" 10 | 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | var ( 15 | publishLive = "live" 16 | publishRecord = "record" 17 | publishAppend = "append" 18 | ) 19 | 20 | var ( 21 | ErrReq = fmt.Errorf("req error") 22 | ) 23 | 24 | var ( 25 | cmdConnect = "connect" 26 | cmdFcpublish = "FCPublish" 27 | cmdReleaseStream = "releaseStream" 28 | cmdCreateStream = "createStream" 29 | cmdPublish = "publish" 30 | cmdFCUnpublish = "FCUnpublish" 31 | cmdDeleteStream = "deleteStream" 32 | cmdPlay = "play" 33 | ) 34 | 35 | type ConnectInfo struct { 36 | App string `amf:"app" json:"app"` 37 | Flashver string `amf:"flashVer" json:"flashVer"` 38 | SwfUrl string `amf:"swfUrl" json:"swfUrl"` 39 | TcUrl string `amf:"tcUrl" json:"tcUrl"` 40 | Fpad bool `amf:"fpad" json:"fpad"` 41 | AudioCodecs int `amf:"audioCodecs" json:"audioCodecs"` 42 | VideoCodecs int `amf:"videoCodecs" json:"videoCodecs"` 43 | VideoFunction int `amf:"videoFunction" json:"videoFunction"` 44 | PageUrl string `amf:"pageUrl" json:"pageUrl"` 45 | ObjectEncoding int `amf:"objectEncoding" json:"objectEncoding"` 46 | } 47 | 48 | type ConnectResp struct { 49 | FMSVer string `amf:"fmsVer"` 50 | Capabilities int `amf:"capabilities"` 51 | } 52 | 53 | type ConnectEvent struct { 54 | Level string `amf:"level"` 55 | Code string `amf:"code"` 56 | Description string `amf:"description"` 57 | ObjectEncoding int `amf:"objectEncoding"` 58 | } 59 | 60 | type PublishInfo struct { 61 | Name string 62 | Type string 63 | } 64 | 65 | type ConnServer struct { 66 | done bool 67 | streamID int 68 | isPublisher bool 69 | conn *Conn 70 | transactionID int 71 | ConnInfo ConnectInfo 72 | PublishInfo PublishInfo 73 | decoder *amf.Decoder 74 | encoder *amf.Encoder 75 | bytesw *bytes.Buffer 76 | } 77 | 78 | func NewConnServer(conn *Conn) *ConnServer { 79 | return &ConnServer{ 80 | conn: conn, 81 | streamID: 1, 82 | bytesw: bytes.NewBuffer(nil), 83 | decoder: &amf.Decoder{}, 84 | encoder: &amf.Encoder{}, 85 | } 86 | } 87 | 88 | func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ...interface{}) error { 89 | connServer.bytesw.Reset() 90 | for _, v := range args { 91 | if _, err := connServer.encoder.Encode(connServer.bytesw, v, amf.AMF0); err != nil { 92 | return err 93 | } 94 | } 95 | msg := connServer.bytesw.Bytes() 96 | c := ChunkStream{ 97 | Format: 0, 98 | CSID: csid, 99 | Timestamp: 0, 100 | TypeID: 20, 101 | StreamID: streamID, 102 | Length: uint32(len(msg)), 103 | Data: msg, 104 | } 105 | connServer.conn.Write(&c) 106 | return connServer.conn.Flush() 107 | } 108 | 109 | func (connServer *ConnServer) connect(vs []interface{}) error { 110 | for _, v := range vs { 111 | switch v.(type) { 112 | case string: 113 | case float64: 114 | id := int(v.(float64)) 115 | if id != 1 { 116 | return ErrReq 117 | } 118 | connServer.transactionID = id 119 | case amf.Object: 120 | obimap := v.(amf.Object) 121 | if app, ok := obimap["app"]; ok { 122 | connServer.ConnInfo.App = app.(string) 123 | } 124 | if flashVer, ok := obimap["flashVer"]; ok { 125 | connServer.ConnInfo.Flashver = flashVer.(string) 126 | } 127 | if tcurl, ok := obimap["tcUrl"]; ok { 128 | connServer.ConnInfo.TcUrl = tcurl.(string) 129 | } 130 | if encoding, ok := obimap["objectEncoding"]; ok { 131 | connServer.ConnInfo.ObjectEncoding = int(encoding.(float64)) 132 | } 133 | } 134 | } 135 | return nil 136 | } 137 | 138 | func (connServer *ConnServer) releaseStream(vs []interface{}) error { 139 | return nil 140 | } 141 | 142 | func (connServer *ConnServer) fcPublish(vs []interface{}) error { 143 | return nil 144 | } 145 | 146 | func (connServer *ConnServer) connectResp(cur *ChunkStream) error { 147 | c := connServer.conn.NewWindowAckSize(2500000) 148 | connServer.conn.Write(&c) 149 | c = connServer.conn.NewSetPeerBandwidth(2500000) 150 | connServer.conn.Write(&c) 151 | c = connServer.conn.NewSetChunkSize(uint32(1024)) 152 | connServer.conn.Write(&c) 153 | 154 | resp := make(amf.Object) 155 | resp["fmsVer"] = "FMS/3,0,1,123" 156 | resp["capabilities"] = 31 157 | 158 | event := make(amf.Object) 159 | event["level"] = "status" 160 | event["code"] = "NetConnection.Connect.Success" 161 | event["description"] = "Connection succeeded." 162 | event["objectEncoding"] = connServer.ConnInfo.ObjectEncoding 163 | return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, resp, event) 164 | } 165 | 166 | func (connServer *ConnServer) createStream(vs []interface{}) error { 167 | for _, v := range vs { 168 | switch v.(type) { 169 | case string: 170 | case float64: 171 | connServer.transactionID = int(v.(float64)) 172 | case amf.Object: 173 | } 174 | } 175 | return nil 176 | } 177 | 178 | func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error { 179 | return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, nil, connServer.streamID) 180 | } 181 | 182 | func (connServer *ConnServer) publishOrPlay(vs []interface{}) error { 183 | for k, v := range vs { 184 | switch v.(type) { 185 | case string: 186 | if k == 2 { 187 | connServer.PublishInfo.Name = v.(string) 188 | } else if k == 3 { 189 | connServer.PublishInfo.Type = v.(string) 190 | } 191 | case float64: 192 | id := int(v.(float64)) 193 | connServer.transactionID = id 194 | case amf.Object: 195 | } 196 | } 197 | 198 | return nil 199 | } 200 | 201 | func (connServer *ConnServer) publishResp(cur *ChunkStream) error { 202 | event := make(amf.Object) 203 | event["level"] = "status" 204 | event["code"] = "NetStream.Publish.Start" 205 | event["description"] = "Start publishing." 206 | return connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event) 207 | } 208 | 209 | func (connServer *ConnServer) playResp(cur *ChunkStream) error { 210 | connServer.conn.SetRecorded() 211 | connServer.conn.SetBegin() 212 | 213 | event := make(amf.Object) 214 | event["level"] = "status" 215 | event["code"] = "NetStream.Play.Reset" 216 | event["description"] = "Playing and resetting stream." 217 | if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { 218 | return err 219 | } 220 | 221 | event["level"] = "status" 222 | event["code"] = "NetStream.Play.Start" 223 | event["description"] = "Started playing stream." 224 | if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { 225 | return err 226 | } 227 | 228 | event["level"] = "status" 229 | event["code"] = "NetStream.Data.Start" 230 | event["description"] = "Started playing stream." 231 | if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { 232 | return err 233 | } 234 | 235 | event["level"] = "status" 236 | event["code"] = "NetStream.Play.PublishNotify" 237 | event["description"] = "Started playing notify." 238 | if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { 239 | return err 240 | } 241 | return connServer.conn.Flush() 242 | } 243 | 244 | func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error { 245 | amfType := amf.AMF0 246 | if c.TypeID == 17 { 247 | c.Data = c.Data[1:] 248 | } 249 | r := bytes.NewReader(c.Data) 250 | vs, err := connServer.decoder.DecodeBatch(r, amf.Version(amfType)) 251 | if err != nil && err != io.EOF { 252 | return err 253 | } 254 | // log.Debugf("rtmp req: %#v", vs) 255 | switch vs[0].(type) { 256 | case string: 257 | switch vs[0].(string) { 258 | case cmdConnect: 259 | if err = connServer.connect(vs[1:]); err != nil { 260 | return err 261 | } 262 | if err = connServer.connectResp(c); err != nil { 263 | return err 264 | } 265 | case cmdCreateStream: 266 | if err = connServer.createStream(vs[1:]); err != nil { 267 | return err 268 | } 269 | if err = connServer.createStreamResp(c); err != nil { 270 | return err 271 | } 272 | case cmdPublish: 273 | if err = connServer.publishOrPlay(vs[1:]); err != nil { 274 | return err 275 | } 276 | if err = connServer.publishResp(c); err != nil { 277 | return err 278 | } 279 | connServer.done = true 280 | connServer.isPublisher = true 281 | log.Debug("handle publish req done") 282 | case cmdPlay: 283 | if err = connServer.publishOrPlay(vs[1:]); err != nil { 284 | return err 285 | } 286 | if err = connServer.playResp(c); err != nil { 287 | return err 288 | } 289 | connServer.done = true 290 | connServer.isPublisher = false 291 | log.Debug("handle play req done") 292 | case cmdFcpublish: 293 | connServer.fcPublish(vs) 294 | case cmdReleaseStream: 295 | connServer.releaseStream(vs) 296 | case cmdFCUnpublish: 297 | case cmdDeleteStream: 298 | default: 299 | log.Debug("no support command=", vs[0].(string)) 300 | } 301 | } 302 | 303 | return nil 304 | } 305 | 306 | func (connServer *ConnServer) ReadMsg() error { 307 | var c ChunkStream 308 | for { 309 | if err := connServer.conn.Read(&c); err != nil { 310 | return err 311 | } 312 | switch c.TypeID { 313 | case 20, 17: 314 | if err := connServer.handleCmdMsg(&c); err != nil { 315 | return err 316 | } 317 | } 318 | if connServer.done { 319 | break 320 | } 321 | } 322 | return nil 323 | } 324 | 325 | func (connServer *ConnServer) IsPublisher() bool { 326 | return connServer.isPublisher 327 | } 328 | 329 | func (connServer *ConnServer) Write(c ChunkStream) error { 330 | if c.TypeID == av.TAG_SCRIPTDATAAMF0 || 331 | c.TypeID == av.TAG_SCRIPTDATAAMF3 { 332 | var err error 333 | if c.Data, err = amf.MetaDataReform(c.Data, amf.DEL); err != nil { 334 | return err 335 | } 336 | c.Length = uint32(len(c.Data)) 337 | } 338 | return connServer.conn.Write(&c) 339 | } 340 | 341 | func (connServer *ConnServer) Flush() error { 342 | return connServer.conn.Flush() 343 | } 344 | 345 | func (connServer *ConnServer) Read(c *ChunkStream) (err error) { 346 | return connServer.conn.Read(c) 347 | } 348 | 349 | func (connServer *ConnServer) GetInfo() (app string, name string, url string) { 350 | app = connServer.ConnInfo.App 351 | name = connServer.PublishInfo.Name 352 | url = connServer.ConnInfo.TcUrl + "/" + connServer.PublishInfo.Name 353 | return 354 | } 355 | 356 | func (connServer *ConnServer) Close(err error) { 357 | connServer.conn.Close() 358 | } 359 | -------------------------------------------------------------------------------- /protocol/rtmp/core/conn_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/gwuhaolin/livego/utils/pool" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestConnReadNormal(t *testing.T) { 14 | at := assert.New(t) 15 | data := []byte{ 16 | 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 17 | } 18 | data1 := make([]byte, 128) 19 | data2 := make([]byte, 51) 20 | data = append(data, data1...) 21 | data = append(data, 0xc6) 22 | data = append(data, data1...) 23 | data = append(data, 0xc6) 24 | data = append(data, data2...) 25 | conn := &Conn{ 26 | pool: pool.NewPool(), 27 | rw: NewReadWriter(bytes.NewBuffer(data), 1024), 28 | remoteChunkSize: 128, 29 | windowAckSize: 2500000, 30 | remoteWindowAckSize: 2500000, 31 | chunks: make(map[uint32]ChunkStream), 32 | } 33 | var c ChunkStream 34 | err := conn.Read(&c) 35 | at.Equal(err, nil) 36 | at.Equal(int(c.CSID), 6) 37 | at.Equal(int(c.Length), 307) 38 | at.Equal(int(c.TypeID), 9) 39 | } 40 | 41 | //交叉读音视频数据 42 | func TestConnCrossReading(t *testing.T) { 43 | at := assert.New(t) 44 | data1 := make([]byte, 128) 45 | data2 := make([]byte, 51) 46 | 47 | videoData := []byte{ 48 | 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 49 | } 50 | audioData := []byte{ 51 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x08, 0x01, 0x00, 0x00, 0x00, 52 | } 53 | //video 1 54 | videoData = append(videoData, data1...) 55 | //video 2 56 | videoData = append(videoData, 0xc6) 57 | videoData = append(videoData, data1...) 58 | //audio 1 59 | videoData = append(videoData, audioData...) 60 | videoData = append(videoData, data1...) 61 | //audio 2 62 | videoData = append(videoData, 0xc4) 63 | videoData = append(videoData, data1...) 64 | //video 3 65 | videoData = append(videoData, 0xc6) 66 | videoData = append(videoData, data2...) 67 | //audio 3 68 | videoData = append(videoData, 0xc4) 69 | videoData = append(videoData, data2...) 70 | 71 | conn := &Conn{ 72 | pool: pool.NewPool(), 73 | rw: NewReadWriter(bytes.NewBuffer(videoData), 1024), 74 | remoteChunkSize: 128, 75 | windowAckSize: 2500000, 76 | remoteWindowAckSize: 2500000, 77 | chunks: make(map[uint32]ChunkStream), 78 | } 79 | var c ChunkStream 80 | //video 1 81 | err := conn.Read(&c) 82 | at.Equal(err, nil) 83 | at.Equal(int(c.TypeID), 9) 84 | at.Equal(len(c.Data), 307) 85 | 86 | //audio2 87 | err = conn.Read(&c) 88 | at.Equal(err, nil) 89 | at.Equal(int(c.TypeID), 8) 90 | at.Equal(len(c.Data), 307) 91 | 92 | err = conn.Read(&c) 93 | at.Equal(err, io.EOF) 94 | } 95 | 96 | func TestSetChunksizeForWrite(t *testing.T) { 97 | at := assert.New(t) 98 | chunk := ChunkStream{ 99 | Format: 0, 100 | CSID: 2, 101 | Timestamp: 0, 102 | Length: 4, 103 | StreamID: 1, 104 | TypeID: idSetChunkSize, 105 | Data: []byte{0x00, 0x00, 0x00, 0x96}, 106 | } 107 | buf := bytes.NewBuffer(nil) 108 | rw := NewReadWriter(buf, 1024) 109 | conn := &Conn{ 110 | pool: pool.NewPool(), 111 | rw: rw, 112 | chunkSize: 128, 113 | remoteChunkSize: 128, 114 | windowAckSize: 2500000, 115 | remoteWindowAckSize: 2500000, 116 | chunks: make(map[uint32]ChunkStream), 117 | } 118 | 119 | audio := ChunkStream{ 120 | Format: 0, 121 | CSID: 4, 122 | Timestamp: 40, 123 | Length: 133, 124 | StreamID: 1, 125 | TypeID: 0x8, 126 | } 127 | audio.Data = make([]byte, 133) 128 | audio.Data = audio.Data[:133] 129 | audio.Data[0] = 0xff 130 | audio.Data[128] = 0xff 131 | err := conn.Write(&audio) 132 | at.Equal(err, nil) 133 | conn.Flush() 134 | at.Equal(len(buf.Bytes()), 146) 135 | 136 | buf.Reset() 137 | err = conn.Write(&chunk) 138 | at.Equal(err, nil) 139 | conn.Flush() 140 | 141 | buf.Reset() 142 | err = conn.Write(&audio) 143 | at.Equal(err, nil) 144 | conn.Flush() 145 | at.Equal(len(buf.Bytes()), 145) 146 | } 147 | 148 | func TestSetChunksize(t *testing.T) { 149 | at := assert.New(t) 150 | data := []byte{ 151 | 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 152 | } 153 | data1 := make([]byte, 128) 154 | data2 := make([]byte, 51) 155 | data = append(data, data1...) 156 | data = append(data, 0xc6) 157 | data = append(data, data1...) 158 | data = append(data, 0xc6) 159 | data = append(data, data2...) 160 | rw := NewReadWriter(bytes.NewBuffer(data), 1024) 161 | conn := &Conn{ 162 | pool: pool.NewPool(), 163 | rw: rw, 164 | chunkSize: 128, 165 | remoteChunkSize: 128, 166 | windowAckSize: 2500000, 167 | remoteWindowAckSize: 2500000, 168 | chunks: make(map[uint32]ChunkStream), 169 | } 170 | 171 | var c ChunkStream 172 | err := conn.Read(&c) 173 | at.Equal(err, nil) 174 | at.Equal(int(c.TypeID), 9) 175 | at.Equal(int(c.CSID), 6) 176 | at.Equal(int(c.StreamID), 1) 177 | at.Equal(len(c.Data), 307) 178 | 179 | //设置chunksize 180 | chunkBuf := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 181 | 0x00, 0x00, 0x00, 0x96} 182 | conn.rw = NewReadWriter(bytes.NewBuffer(chunkBuf), 1024) 183 | err = conn.Read(&c) 184 | at.Equal(err, nil) 185 | 186 | data = data[:12] 187 | data[7] = 0x8 188 | data1 = make([]byte, 150) 189 | data2 = make([]byte, 7) 190 | data = append(data, data1...) 191 | data = append(data, 0xc6) 192 | data = append(data, data1...) 193 | data = append(data, 0xc6) 194 | data = append(data, data2...) 195 | 196 | conn.rw = NewReadWriter(bytes.NewBuffer(data), 1024) 197 | err = conn.Read(&c) 198 | at.Equal(err, nil) 199 | at.Equal(len(c.Data), 307) 200 | 201 | err = conn.Read(&c) 202 | at.Equal(err, io.EOF) 203 | } 204 | 205 | func TestConnWrite(t *testing.T) { 206 | at := assert.New(t) 207 | wr := bytes.NewBuffer(nil) 208 | readWriter := NewReadWriter(wr, 128) 209 | conn := &Conn{ 210 | pool: pool.NewPool(), 211 | rw: readWriter, 212 | chunkSize: 128, 213 | remoteChunkSize: 128, 214 | windowAckSize: 2500000, 215 | remoteWindowAckSize: 2500000, 216 | chunks: make(map[uint32]ChunkStream), 217 | } 218 | 219 | c1 := ChunkStream{ 220 | Length: 3, 221 | TypeID: 8, 222 | CSID: 3, 223 | Timestamp: 40, 224 | Data: []byte{0x01, 0x02, 0x03}, 225 | } 226 | err := conn.Write(&c1) 227 | at.Equal(err, nil) 228 | conn.Flush() 229 | at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x28, 0x0, 0x0, 0x3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3}) 230 | 231 | //for type 1 232 | wr.Reset() 233 | c1 = ChunkStream{ 234 | Length: 4, 235 | TypeID: 8, 236 | CSID: 3, 237 | Timestamp: 80, 238 | Data: []byte{0x01, 0x02, 0x03, 0x4}, 239 | } 240 | err = conn.Write(&c1) 241 | at.Equal(err, nil) 242 | conn.Flush() 243 | at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x50, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4}) 244 | 245 | //for type 2 246 | wr.Reset() 247 | c1.Timestamp = 160 248 | err = conn.Write(&c1) 249 | at.Equal(err, nil) 250 | conn.Flush() 251 | at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4}) 252 | } 253 | -------------------------------------------------------------------------------- /protocol/rtmp/core/handshake.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "fmt" 9 | "io" 10 | 11 | "time" 12 | 13 | "github.com/gwuhaolin/livego/utils/pio" 14 | ) 15 | 16 | var ( 17 | timeout = 5 * time.Second 18 | ) 19 | 20 | var ( 21 | hsClientFullKey = []byte{ 22 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 23 | 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', 24 | '0', '0', '1', 25 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 26 | 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 27 | 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, 28 | } 29 | hsServerFullKey = []byte{ 30 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 31 | 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', 32 | 'S', 'e', 'r', 'v', 'e', 'r', ' ', 33 | '0', '0', '1', 34 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 35 | 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 36 | 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, 37 | } 38 | hsClientPartialKey = hsClientFullKey[:30] 39 | hsServerPartialKey = hsServerFullKey[:36] 40 | ) 41 | 42 | func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) { 43 | h := hmac.New(sha256.New, key) 44 | if gap <= 0 { 45 | h.Write(src) 46 | } else { 47 | h.Write(src[:gap]) 48 | h.Write(src[gap+32:]) 49 | } 50 | return h.Sum(nil) 51 | } 52 | 53 | func hsCalcDigestPos(p []byte, base int) (pos int) { 54 | for i := 0; i < 4; i++ { 55 | pos += int(p[base+i]) 56 | } 57 | pos = (pos % 728) + base + 4 58 | return 59 | } 60 | 61 | func hsFindDigest(p []byte, key []byte, base int) int { 62 | gap := hsCalcDigestPos(p, base) 63 | digest := hsMakeDigest(key, p, gap) 64 | if bytes.Compare(p[gap:gap+32], digest) != 0 { 65 | return -1 66 | } 67 | return gap 68 | } 69 | 70 | func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []byte) { 71 | var pos int 72 | if pos = hsFindDigest(p, peerkey, 772); pos == -1 { 73 | if pos = hsFindDigest(p, peerkey, 8); pos == -1 { 74 | return 75 | } 76 | } 77 | ok = true 78 | digest = hsMakeDigest(key, p[pos:pos+32], -1) 79 | return 80 | } 81 | 82 | func hsCreate01(p []byte, time uint32, ver uint32, key []byte) { 83 | p[0] = 3 84 | p1 := p[1:] 85 | rand.Read(p1[8:]) 86 | pio.PutU32BE(p1[0:4], time) 87 | pio.PutU32BE(p1[4:8], ver) 88 | gap := hsCalcDigestPos(p1, 8) 89 | digest := hsMakeDigest(key, p1, gap) 90 | copy(p1[gap:], digest) 91 | } 92 | 93 | func hsCreate2(p []byte, key []byte) { 94 | rand.Read(p) 95 | gap := len(p) - 32 96 | digest := hsMakeDigest(key, p, gap) 97 | copy(p[gap:], digest) 98 | } 99 | 100 | func (conn *Conn) HandshakeClient() (err error) { 101 | var random [(1 + 1536*2) * 2]byte 102 | 103 | C0C1C2 := random[:1536*2+1] 104 | C0 := C0C1C2[:1] 105 | C0C1 := C0C1C2[:1536+1] 106 | C2 := C0C1C2[1536+1:] 107 | 108 | S0S1S2 := random[1536*2+1:] 109 | 110 | C0[0] = 3 111 | // > C0C1 112 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 113 | if _, err = conn.rw.Write(C0C1); err != nil { 114 | return 115 | } 116 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 117 | if err = conn.rw.Flush(); err != nil { 118 | return 119 | } 120 | 121 | // < S0S1S2 122 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 123 | if _, err = io.ReadFull(conn.rw, S0S1S2); err != nil { 124 | return 125 | } 126 | 127 | S1 := S0S1S2[1 : 1536+1] 128 | if ver := pio.U32BE(S1[4:8]); ver != 0 { 129 | C2 = S1 130 | } else { 131 | C2 = S1 132 | } 133 | 134 | // > C2 135 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 136 | if _, err = conn.rw.Write(C2); err != nil { 137 | return 138 | } 139 | conn.Conn.SetDeadline(time.Time{}) 140 | return 141 | } 142 | 143 | func (conn *Conn) HandshakeServer() (err error) { 144 | var random [(1 + 1536*2) * 2]byte 145 | 146 | C0C1C2 := random[:1536*2+1] 147 | C0 := C0C1C2[:1] 148 | C1 := C0C1C2[1 : 1536+1] 149 | C0C1 := C0C1C2[:1536+1] 150 | C2 := C0C1C2[1536+1:] 151 | 152 | S0S1S2 := random[1536*2+1:] 153 | S0 := S0S1S2[:1] 154 | S1 := S0S1S2[1 : 1536+1] 155 | S0S1 := S0S1S2[:1536+1] 156 | S2 := S0S1S2[1536+1:] 157 | 158 | // < C0C1 159 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 160 | if _, err = io.ReadFull(conn.rw, C0C1); err != nil { 161 | return 162 | } 163 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 164 | if C0[0] != 3 { 165 | err = fmt.Errorf("rtmp: handshake version=%d invalid", C0[0]) 166 | return 167 | } 168 | 169 | S0[0] = 3 170 | 171 | clitime := pio.U32BE(C1[0:4]) 172 | srvtime := clitime 173 | srvver := uint32(0x0d0e0a0d) 174 | cliver := pio.U32BE(C1[4:8]) 175 | 176 | if cliver != 0 { 177 | var ok bool 178 | var digest []byte 179 | if ok, digest = hsParse1(C1, hsClientPartialKey, hsServerFullKey); !ok { 180 | err = fmt.Errorf("rtmp: handshake server: C1 invalid") 181 | return 182 | } 183 | hsCreate01(S0S1, srvtime, srvver, hsServerPartialKey) 184 | hsCreate2(S2, digest) 185 | } else { 186 | copy(S1, C2) 187 | copy(S2, C1) 188 | } 189 | 190 | // > S0S1S2 191 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 192 | if _, err = conn.rw.Write(S0S1S2); err != nil { 193 | return 194 | } 195 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 196 | if err = conn.rw.Flush(); err != nil { 197 | return 198 | } 199 | 200 | // < C2 201 | conn.Conn.SetDeadline(time.Now().Add(timeout)) 202 | if _, err = io.ReadFull(conn.rw, C2); err != nil { 203 | return 204 | } 205 | conn.Conn.SetDeadline(time.Time{}) 206 | return 207 | } 208 | -------------------------------------------------------------------------------- /protocol/rtmp/core/read_writer.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | ) 7 | 8 | type ReadWriter struct { 9 | *bufio.ReadWriter 10 | readError error 11 | writeError error 12 | } 13 | 14 | func NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter { 15 | return &ReadWriter{ 16 | ReadWriter: bufio.NewReadWriter(bufio.NewReaderSize(rw, bufSize), bufio.NewWriterSize(rw, bufSize)), 17 | } 18 | } 19 | 20 | func (rw *ReadWriter) Read(p []byte) (int, error) { 21 | if rw.readError != nil { 22 | return 0, rw.readError 23 | } 24 | n, err := io.ReadAtLeast(rw.ReadWriter, p, len(p)) 25 | rw.readError = err 26 | return n, err 27 | } 28 | 29 | func (rw *ReadWriter) ReadError() error { 30 | return rw.readError 31 | } 32 | 33 | func (rw *ReadWriter) ReadUintBE(n int) (uint32, error) { 34 | if rw.readError != nil { 35 | return 0, rw.readError 36 | } 37 | ret := uint32(0) 38 | for i := 0; i < n; i++ { 39 | b, err := rw.ReadByte() 40 | if err != nil { 41 | rw.readError = err 42 | return 0, err 43 | } 44 | ret = ret<<8 + uint32(b) 45 | } 46 | return ret, nil 47 | } 48 | 49 | func (rw *ReadWriter) ReadUintLE(n int) (uint32, error) { 50 | if rw.readError != nil { 51 | return 0, rw.readError 52 | } 53 | ret := uint32(0) 54 | for i := 0; i < n; i++ { 55 | b, err := rw.ReadByte() 56 | if err != nil { 57 | rw.readError = err 58 | return 0, err 59 | } 60 | ret += uint32(b) << uint32(i*8) 61 | } 62 | return ret, nil 63 | } 64 | 65 | func (rw *ReadWriter) Flush() error { 66 | if rw.writeError != nil { 67 | return rw.writeError 68 | } 69 | 70 | if rw.ReadWriter.Writer.Buffered() == 0 { 71 | return nil 72 | } 73 | return rw.ReadWriter.Flush() 74 | } 75 | 76 | func (rw *ReadWriter) Write(p []byte) (int, error) { 77 | if rw.writeError != nil { 78 | return 0, rw.writeError 79 | } 80 | return rw.ReadWriter.Write(p) 81 | } 82 | 83 | func (rw *ReadWriter) WriteError() error { 84 | return rw.writeError 85 | } 86 | 87 | func (rw *ReadWriter) WriteUintBE(v uint32, n int) error { 88 | if rw.writeError != nil { 89 | return rw.writeError 90 | } 91 | for i := 0; i < n; i++ { 92 | b := byte(v>>uint32((n-i-1)<<3)) & 0xff 93 | if err := rw.WriteByte(b); err != nil { 94 | rw.writeError = err 95 | return err 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | func (rw *ReadWriter) WriteUintLE(v uint32, n int) error { 102 | if rw.writeError != nil { 103 | return rw.writeError 104 | } 105 | for i := 0; i < n; i++ { 106 | b := byte(v) & 0xff 107 | if err := rw.WriteByte(b); err != nil { 108 | rw.writeError = err 109 | return err 110 | } 111 | v = v >> 8 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /protocol/rtmp/core/read_writer_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestReader(t *testing.T) { 12 | at := assert.New(t) 13 | buf := bytes.NewBufferString("abc") 14 | r := NewReadWriter(buf, 1024) 15 | b := make([]byte, 3) 16 | n, err := r.Read(b) 17 | at.Equal(err, nil) 18 | at.Equal(r.ReadError(), nil) 19 | at.Equal(n, 3) 20 | n, err = r.Read(b) 21 | at.Equal(err, io.EOF) 22 | at.Equal(r.ReadError(), io.EOF) 23 | buf.WriteString("123") 24 | n, err = r.Read(b) 25 | at.Equal(err, io.EOF) 26 | at.Equal(r.ReadError(), io.EOF) 27 | at.Equal(n, 0) 28 | } 29 | 30 | func TestReaderUintBE(t *testing.T) { 31 | at := assert.New(t) 32 | type Test struct { 33 | i int 34 | value uint32 35 | bytes []byte 36 | } 37 | tests := []Test{ 38 | {1, 0x01, []byte{0x01}}, 39 | {2, 0x0102, []byte{0x01, 0x02}}, 40 | {3, 0x010203, []byte{0x01, 0x02, 0x03}}, 41 | {4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}}, 42 | } 43 | for _, test := range tests { 44 | buf := bytes.NewBuffer(test.bytes) 45 | r := NewReadWriter(buf, 1024) 46 | n, err := r.ReadUintBE(test.i) 47 | at.Equal(err, nil, "test %d", test.i) 48 | at.Equal(n, test.value, "test %d", test.i) 49 | } 50 | } 51 | 52 | func TestReaderUintLE(t *testing.T) { 53 | at := assert.New(t) 54 | type Test struct { 55 | i int 56 | value uint32 57 | bytes []byte 58 | } 59 | tests := []Test{ 60 | {1, 0x01, []byte{0x01}}, 61 | {2, 0x0102, []byte{0x02, 0x01}}, 62 | {3, 0x010203, []byte{0x03, 0x02, 0x01}}, 63 | {4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}}, 64 | } 65 | for _, test := range tests { 66 | buf := bytes.NewBuffer(test.bytes) 67 | r := NewReadWriter(buf, 1024) 68 | n, err := r.ReadUintLE(test.i) 69 | at.Equal(err, nil, "test %d", test.i) 70 | at.Equal(n, test.value, "test %d", test.i) 71 | } 72 | } 73 | 74 | func TestWriter(t *testing.T) { 75 | at := assert.New(t) 76 | buf := bytes.NewBuffer(nil) 77 | w := NewReadWriter(buf, 1024) 78 | b := []byte{1, 2, 3} 79 | n, err := w.Write(b) 80 | at.Equal(err, nil) 81 | at.Equal(w.WriteError(), nil) 82 | at.Equal(n, 3) 83 | w.writeError = io.EOF 84 | n, err = w.Write(b) 85 | at.Equal(err, io.EOF) 86 | at.Equal(w.WriteError(), io.EOF) 87 | at.Equal(n, 0) 88 | } 89 | 90 | func TestWriteUintBE(t *testing.T) { 91 | at := assert.New(t) 92 | type Test struct { 93 | i int 94 | value uint32 95 | bytes []byte 96 | } 97 | tests := []Test{ 98 | {1, 0x01, []byte{0x01}}, 99 | {2, 0x0102, []byte{0x01, 0x02}}, 100 | {3, 0x010203, []byte{0x01, 0x02, 0x03}}, 101 | {4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}}, 102 | } 103 | for _, test := range tests { 104 | buf := bytes.NewBuffer(nil) 105 | r := NewReadWriter(buf, 1024) 106 | err := r.WriteUintBE(test.value, test.i) 107 | at.Equal(err, nil, "test %d", test.i) 108 | err = r.Flush() 109 | at.Equal(err, nil, "test %d", test.i) 110 | at.Equal(buf.Bytes(), test.bytes, "test %d", test.i) 111 | } 112 | } 113 | 114 | func TestWriteUintLE(t *testing.T) { 115 | at := assert.New(t) 116 | type Test struct { 117 | i int 118 | value uint32 119 | bytes []byte 120 | } 121 | tests := []Test{ 122 | {1, 0x01, []byte{0x01}}, 123 | {2, 0x0102, []byte{0x02, 0x01}}, 124 | {3, 0x010203, []byte{0x03, 0x02, 0x01}}, 125 | {4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}}, 126 | } 127 | for _, test := range tests { 128 | buf := bytes.NewBuffer(nil) 129 | r := NewReadWriter(buf, 1024) 130 | err := r.WriteUintLE(test.value, test.i) 131 | at.Equal(err, nil, "test %d", test.i) 132 | err = r.Flush() 133 | at.Equal(err, nil, "test %d", test.i) 134 | at.Equal(buf.Bytes(), test.bytes, "test %d", test.i) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /protocol/rtmp/rtmprelay/rtmprelay.go: -------------------------------------------------------------------------------- 1 | package rtmprelay 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/gwuhaolin/livego/av" 9 | "github.com/gwuhaolin/livego/protocol/amf" 10 | "github.com/gwuhaolin/livego/protocol/rtmp/core" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | STOP_CTRL = "RTMPRELAY_STOP" 17 | ) 18 | 19 | type RtmpRelay struct { 20 | PlayUrl string 21 | PublishUrl string 22 | cs_chan chan core.ChunkStream 23 | sndctrl_chan chan string 24 | connectPlayClient *core.ConnClient 25 | connectPublishClient *core.ConnClient 26 | startflag bool 27 | } 28 | 29 | func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay { 30 | return &RtmpRelay{ 31 | PlayUrl: *playurl, 32 | PublishUrl: *publishurl, 33 | cs_chan: make(chan core.ChunkStream, 500), 34 | sndctrl_chan: make(chan string), 35 | connectPlayClient: nil, 36 | connectPublishClient: nil, 37 | startflag: false, 38 | } 39 | } 40 | 41 | func (self *RtmpRelay) rcvPlayChunkStream() { 42 | log.Debug("rcvPlayRtmpMediaPacket connectClient.Read...") 43 | for { 44 | var rc core.ChunkStream 45 | 46 | if self.startflag == false { 47 | self.connectPlayClient.Close(nil) 48 | log.Debugf("rcvPlayChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) 49 | break 50 | } 51 | err := self.connectPlayClient.Read(&rc) 52 | 53 | if err != nil && err == io.EOF { 54 | break 55 | } 56 | //log.Debugf("connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v", rc.TypeID, len(rc.Data), err) 57 | switch rc.TypeID { 58 | case 20, 17: 59 | r := bytes.NewReader(rc.Data) 60 | vs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0) 61 | 62 | log.Debugf("rcvPlayRtmpMediaPacket: vs=%v, err=%v", vs, err) 63 | case 18: 64 | log.Debug("rcvPlayRtmpMediaPacket: metadata....") 65 | self.cs_chan <- rc 66 | case 8, 9: 67 | self.cs_chan <- rc 68 | } 69 | } 70 | } 71 | 72 | func (self *RtmpRelay) sendPublishChunkStream() { 73 | for { 74 | select { 75 | case rc := <-self.cs_chan: 76 | //log.Debugf("sendPublishChunkStream: rc.TypeID=%v length=%d", rc.TypeID, len(rc.Data)) 77 | self.connectPublishClient.Write(rc) 78 | case ctrlcmd := <-self.sndctrl_chan: 79 | if ctrlcmd == STOP_CTRL { 80 | self.connectPublishClient.Close(nil) 81 | log.Debugf("sendPublishChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) 82 | return 83 | } 84 | } 85 | } 86 | } 87 | 88 | func (self *RtmpRelay) Start() error { 89 | if self.startflag { 90 | return fmt.Errorf("The rtmprelay already started, playurl=%s, publishurl=%s\n", self.PlayUrl, self.PublishUrl) 91 | } 92 | 93 | self.connectPlayClient = core.NewConnClient() 94 | self.connectPublishClient = core.NewConnClient() 95 | 96 | log.Debugf("play server addr:%v starting....", self.PlayUrl) 97 | err := self.connectPlayClient.Start(self.PlayUrl, av.PLAY) 98 | if err != nil { 99 | log.Debugf("connectPlayClient.Start url=%v error", self.PlayUrl) 100 | return err 101 | } 102 | 103 | log.Debugf("publish server addr:%v starting....", self.PublishUrl) 104 | err = self.connectPublishClient.Start(self.PublishUrl, av.PUBLISH) 105 | if err != nil { 106 | log.Debugf("connectPublishClient.Start url=%v error", self.PublishUrl) 107 | self.connectPlayClient.Close(nil) 108 | return err 109 | } 110 | 111 | self.startflag = true 112 | go self.rcvPlayChunkStream() 113 | go self.sendPublishChunkStream() 114 | 115 | return nil 116 | } 117 | 118 | func (self *RtmpRelay) Stop() { 119 | if !self.startflag { 120 | log.Debugf("The rtmprelay already stoped, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) 121 | return 122 | } 123 | 124 | self.startflag = false 125 | self.sndctrl_chan <- STOP_CTRL 126 | } 127 | -------------------------------------------------------------------------------- /protocol/rtmp/rtmprelay/staticrelay.go: -------------------------------------------------------------------------------- 1 | package rtmprelay 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/gwuhaolin/livego/av" 8 | "github.com/gwuhaolin/livego/configure" 9 | "github.com/gwuhaolin/livego/protocol/rtmp/core" 10 | 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type StaticPush struct { 15 | RtmpUrl string 16 | packet_chan chan *av.Packet 17 | sndctrl_chan chan string 18 | connectClient *core.ConnClient 19 | startflag bool 20 | } 21 | 22 | var G_StaticPushMap = make(map[string](*StaticPush)) 23 | var g_MapLock = new(sync.RWMutex) 24 | var G_PushUrlList []string = nil 25 | 26 | var ( 27 | STATIC_RELAY_STOP_CTRL = "STATIC_RTMPRELAY_STOP" 28 | ) 29 | 30 | func GetStaticPushList(appname string) ([]string, error) { 31 | if G_PushUrlList == nil { 32 | // Do not unmarshel the config every time, lots of reflect works -gs 33 | pushurlList, ok := configure.GetStaticPushUrlList(appname) 34 | if !ok { 35 | G_PushUrlList = []string{} 36 | } else { 37 | G_PushUrlList = pushurlList 38 | } 39 | } 40 | 41 | if len(G_PushUrlList) == 0 { 42 | return nil, fmt.Errorf("no static push url") 43 | } 44 | 45 | return G_PushUrlList, nil 46 | } 47 | 48 | func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush { 49 | g_MapLock.RLock() 50 | staticpush, ok := G_StaticPushMap[rtmpurl] 51 | log.Debugf("GetAndCreateStaticPushObject: %s, return %v", rtmpurl, ok) 52 | if !ok { 53 | g_MapLock.RUnlock() 54 | newStaticpush := NewStaticPush(rtmpurl) 55 | 56 | g_MapLock.Lock() 57 | G_StaticPushMap[rtmpurl] = newStaticpush 58 | g_MapLock.Unlock() 59 | 60 | return newStaticpush 61 | } 62 | g_MapLock.RUnlock() 63 | 64 | return staticpush 65 | } 66 | 67 | func GetStaticPushObject(rtmpurl string) (*StaticPush, error) { 68 | g_MapLock.RLock() 69 | if staticpush, ok := G_StaticPushMap[rtmpurl]; ok { 70 | g_MapLock.RUnlock() 71 | return staticpush, nil 72 | } 73 | g_MapLock.RUnlock() 74 | 75 | return nil, fmt.Errorf("G_StaticPushMap[%s] not exist....", rtmpurl) 76 | } 77 | 78 | func ReleaseStaticPushObject(rtmpurl string) { 79 | g_MapLock.RLock() 80 | if _, ok := G_StaticPushMap[rtmpurl]; ok { 81 | g_MapLock.RUnlock() 82 | 83 | log.Debugf("ReleaseStaticPushObject %s ok", rtmpurl) 84 | g_MapLock.Lock() 85 | delete(G_StaticPushMap, rtmpurl) 86 | g_MapLock.Unlock() 87 | } else { 88 | g_MapLock.RUnlock() 89 | log.Debugf("ReleaseStaticPushObject: not find %s", rtmpurl) 90 | } 91 | } 92 | 93 | func NewStaticPush(rtmpurl string) *StaticPush { 94 | return &StaticPush{ 95 | RtmpUrl: rtmpurl, 96 | packet_chan: make(chan *av.Packet, 500), 97 | sndctrl_chan: make(chan string), 98 | connectClient: nil, 99 | startflag: false, 100 | } 101 | } 102 | 103 | func (self *StaticPush) Start() error { 104 | if self.startflag { 105 | return fmt.Errorf("StaticPush already start %s", self.RtmpUrl) 106 | } 107 | 108 | self.connectClient = core.NewConnClient() 109 | 110 | log.Debugf("static publish server addr:%v starting....", self.RtmpUrl) 111 | err := self.connectClient.Start(self.RtmpUrl, "publish") 112 | if err != nil { 113 | log.Debugf("connectClient.Start url=%v error", self.RtmpUrl) 114 | return err 115 | } 116 | log.Debugf("static publish server addr:%v started, streamid=%d", self.RtmpUrl, self.connectClient.GetStreamId()) 117 | go self.HandleAvPacket() 118 | 119 | self.startflag = true 120 | return nil 121 | } 122 | 123 | func (self *StaticPush) Stop() { 124 | if !self.startflag { 125 | return 126 | } 127 | 128 | log.Debugf("StaticPush Stop: %s", self.RtmpUrl) 129 | self.sndctrl_chan <- STATIC_RELAY_STOP_CTRL 130 | self.startflag = false 131 | } 132 | 133 | func (self *StaticPush) WriteAvPacket(packet *av.Packet) { 134 | if !self.startflag { 135 | return 136 | } 137 | 138 | self.packet_chan <- packet 139 | } 140 | 141 | func (self *StaticPush) sendPacket(p *av.Packet) { 142 | if !self.startflag { 143 | return 144 | } 145 | var cs core.ChunkStream 146 | 147 | cs.Data = p.Data 148 | cs.Length = uint32(len(p.Data)) 149 | cs.StreamID = self.connectClient.GetStreamId() 150 | cs.Timestamp = p.TimeStamp 151 | //cs.Timestamp += v.BaseTimeStamp() 152 | 153 | //log.Printf("Static sendPacket: rtmpurl=%s, length=%d, streamid=%d", 154 | // self.RtmpUrl, len(p.Data), cs.StreamID) 155 | if p.IsVideo { 156 | cs.TypeID = av.TAG_VIDEO 157 | } else { 158 | if p.IsMetadata { 159 | cs.TypeID = av.TAG_SCRIPTDATAAMF0 160 | } else { 161 | cs.TypeID = av.TAG_AUDIO 162 | } 163 | } 164 | 165 | self.connectClient.Write(cs) 166 | } 167 | 168 | func (self *StaticPush) HandleAvPacket() { 169 | if !self.IsStart() { 170 | log.Debugf("static push %s not started", self.RtmpUrl) 171 | return 172 | } 173 | 174 | for { 175 | select { 176 | case packet := <-self.packet_chan: 177 | self.sendPacket(packet) 178 | case ctrlcmd := <-self.sndctrl_chan: 179 | if ctrlcmd == STATIC_RELAY_STOP_CTRL { 180 | self.connectClient.Close(nil) 181 | log.Debugf("Static HandleAvPacket close: publishurl=%s", self.RtmpUrl) 182 | return 183 | } 184 | } 185 | } 186 | } 187 | 188 | func (self *StaticPush) IsStart() bool { 189 | return self.startflag 190 | } 191 | -------------------------------------------------------------------------------- /test.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /utils/pio/pio.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | var RecommendBufioSize = 1024 * 64 4 | -------------------------------------------------------------------------------- /utils/pio/reader.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | func U8(b []byte) (i uint8) { 4 | return b[0] 5 | } 6 | 7 | func U16BE(b []byte) (i uint16) { 8 | i = uint16(b[0]) 9 | i <<= 8 10 | i |= uint16(b[1]) 11 | return 12 | } 13 | 14 | func I16BE(b []byte) (i int16) { 15 | i = int16(b[0]) 16 | i <<= 8 17 | i |= int16(b[1]) 18 | return 19 | } 20 | 21 | func I24BE(b []byte) (i int32) { 22 | i = int32(int8(b[0])) 23 | i <<= 8 24 | i |= int32(b[1]) 25 | i <<= 8 26 | i |= int32(b[2]) 27 | return 28 | } 29 | 30 | func U24BE(b []byte) (i uint32) { 31 | i = uint32(b[0]) 32 | i <<= 8 33 | i |= uint32(b[1]) 34 | i <<= 8 35 | i |= uint32(b[2]) 36 | return 37 | } 38 | 39 | func I32BE(b []byte) (i int32) { 40 | i = int32(int8(b[0])) 41 | i <<= 8 42 | i |= int32(b[1]) 43 | i <<= 8 44 | i |= int32(b[2]) 45 | i <<= 8 46 | i |= int32(b[3]) 47 | return 48 | } 49 | 50 | func U32LE(b []byte) (i uint32) { 51 | i = uint32(b[3]) 52 | i <<= 8 53 | i |= uint32(b[2]) 54 | i <<= 8 55 | i |= uint32(b[1]) 56 | i <<= 8 57 | i |= uint32(b[0]) 58 | return 59 | } 60 | 61 | func U32BE(b []byte) (i uint32) { 62 | i = uint32(b[0]) 63 | i <<= 8 64 | i |= uint32(b[1]) 65 | i <<= 8 66 | i |= uint32(b[2]) 67 | i <<= 8 68 | i |= uint32(b[3]) 69 | return 70 | } 71 | 72 | func U40BE(b []byte) (i uint64) { 73 | i = uint64(b[0]) 74 | i <<= 8 75 | i |= uint64(b[1]) 76 | i <<= 8 77 | i |= uint64(b[2]) 78 | i <<= 8 79 | i |= uint64(b[3]) 80 | i <<= 8 81 | i |= uint64(b[4]) 82 | return 83 | } 84 | 85 | func U64BE(b []byte) (i uint64) { 86 | i = uint64(b[0]) 87 | i <<= 8 88 | i |= uint64(b[1]) 89 | i <<= 8 90 | i |= uint64(b[2]) 91 | i <<= 8 92 | i |= uint64(b[3]) 93 | i <<= 8 94 | i |= uint64(b[4]) 95 | i <<= 8 96 | i |= uint64(b[5]) 97 | i <<= 8 98 | i |= uint64(b[6]) 99 | i <<= 8 100 | i |= uint64(b[7]) 101 | return 102 | } 103 | 104 | func I64BE(b []byte) (i int64) { 105 | i = int64(int8(b[0])) 106 | i <<= 8 107 | i |= int64(b[1]) 108 | i <<= 8 109 | i |= int64(b[2]) 110 | i <<= 8 111 | i |= int64(b[3]) 112 | i <<= 8 113 | i |= int64(b[4]) 114 | i <<= 8 115 | i |= int64(b[5]) 116 | i <<= 8 117 | i |= int64(b[6]) 118 | i <<= 8 119 | i |= int64(b[7]) 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /utils/pio/writer.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | func PutU8(b []byte, v uint8) { 4 | b[0] = v 5 | } 6 | 7 | func PutI16BE(b []byte, v int16) { 8 | b[0] = byte(v >> 8) 9 | b[1] = byte(v) 10 | } 11 | 12 | func PutU16BE(b []byte, v uint16) { 13 | b[0] = byte(v >> 8) 14 | b[1] = byte(v) 15 | } 16 | 17 | func PutI24BE(b []byte, v int32) { 18 | b[0] = byte(v >> 16) 19 | b[1] = byte(v >> 8) 20 | b[2] = byte(v) 21 | } 22 | 23 | func PutU24BE(b []byte, v uint32) { 24 | b[0] = byte(v >> 16) 25 | b[1] = byte(v >> 8) 26 | b[2] = byte(v) 27 | } 28 | 29 | func PutI32BE(b []byte, v int32) { 30 | b[0] = byte(v >> 24) 31 | b[1] = byte(v >> 16) 32 | b[2] = byte(v >> 8) 33 | b[3] = byte(v) 34 | } 35 | 36 | func PutU32BE(b []byte, v uint32) { 37 | b[0] = byte(v >> 24) 38 | b[1] = byte(v >> 16) 39 | b[2] = byte(v >> 8) 40 | b[3] = byte(v) 41 | } 42 | 43 | func PutU32LE(b []byte, v uint32) { 44 | b[3] = byte(v >> 24) 45 | b[2] = byte(v >> 16) 46 | b[1] = byte(v >> 8) 47 | b[0] = byte(v) 48 | } 49 | 50 | func PutU40BE(b []byte, v uint64) { 51 | b[0] = byte(v >> 32) 52 | b[1] = byte(v >> 24) 53 | b[2] = byte(v >> 16) 54 | b[3] = byte(v >> 8) 55 | b[4] = byte(v) 56 | } 57 | 58 | func PutU48BE(b []byte, v uint64) { 59 | b[0] = byte(v >> 40) 60 | b[1] = byte(v >> 32) 61 | b[2] = byte(v >> 24) 62 | b[3] = byte(v >> 16) 63 | b[4] = byte(v >> 8) 64 | b[5] = byte(v) 65 | } 66 | 67 | func PutU64BE(b []byte, v uint64) { 68 | b[0] = byte(v >> 56) 69 | b[1] = byte(v >> 48) 70 | b[2] = byte(v >> 40) 71 | b[3] = byte(v >> 32) 72 | b[4] = byte(v >> 24) 73 | b[5] = byte(v >> 16) 74 | b[6] = byte(v >> 8) 75 | b[7] = byte(v) 76 | } 77 | 78 | func PutI64BE(b []byte, v int64) { 79 | b[0] = byte(v >> 56) 80 | b[1] = byte(v >> 48) 81 | b[2] = byte(v >> 40) 82 | b[3] = byte(v >> 32) 83 | b[4] = byte(v >> 24) 84 | b[5] = byte(v >> 16) 85 | b[6] = byte(v >> 8) 86 | b[7] = byte(v) 87 | } 88 | -------------------------------------------------------------------------------- /utils/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | type Pool struct { 4 | pos int 5 | buf []byte 6 | } 7 | 8 | const maxpoolsize = 500 * 1024 9 | 10 | func (pool *Pool) Get(size int) []byte { 11 | if maxpoolsize-pool.pos < size { 12 | pool.pos = 0 13 | pool.buf = make([]byte, maxpoolsize) 14 | } 15 | b := pool.buf[pool.pos : pool.pos+size] 16 | pool.pos += size 17 | return b 18 | } 19 | 20 | func NewPool() *Pool { 21 | return &Pool{ 22 | buf: make([]byte, maxpoolsize), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/gwuhaolin/livego/av" 7 | ) 8 | 9 | // Queue is a basic FIFO queue for Messages. 10 | type Queue struct { 11 | maxSize int 12 | 13 | list []*av.Packet 14 | mutex sync.Mutex 15 | } 16 | 17 | // NewQueue returns a new Queue. If maxSize is greater than zero the queue will 18 | // not grow more than the defined size. 19 | func NewQueue(maxSize int) *Queue { 20 | return &Queue{ 21 | maxSize: maxSize, 22 | } 23 | } 24 | 25 | // Push adds a message to the queue. 26 | func (q *Queue) Push(msg *av.Packet) { 27 | q.mutex.Lock() 28 | defer q.mutex.Unlock() 29 | 30 | if len(q.list) == q.maxSize { 31 | q.pop() 32 | } 33 | 34 | q.list = append(q.list, msg) 35 | } 36 | 37 | // Pop removes and returns a message from the queue in first to last order. 38 | func (q *Queue) Pop() *av.Packet { 39 | q.mutex.Lock() 40 | defer q.mutex.Unlock() 41 | 42 | if len(q.list) == 0 { 43 | return nil 44 | } 45 | 46 | return q.pop() 47 | } 48 | 49 | func (q *Queue) pop() *av.Packet { 50 | x := len(q.list) - 1 51 | msg := q.list[x] 52 | q.list = q.list[:x] 53 | return msg 54 | } 55 | 56 | // Len returns the length of the queue. 57 | func (q *Queue) Len() int { 58 | q.mutex.Lock() 59 | defer q.mutex.Unlock() 60 | 61 | return len(q.list) 62 | } 63 | 64 | // All returns and removes all messages from the queue. 65 | func (q *Queue) All() []*av.Packet { 66 | q.mutex.Lock() 67 | defer q.mutex.Unlock() 68 | 69 | cache := q.list 70 | q.list = nil 71 | return cache 72 | } 73 | -------------------------------------------------------------------------------- /utils/uid/rand.go: -------------------------------------------------------------------------------- 1 | package uid 2 | 3 | import "math/rand" 4 | 5 | var letterRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 6 | 7 | func RandStringRunes(n int) string { 8 | b := make([]rune, n) 9 | for i := range b { 10 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 11 | } 12 | return string(b) 13 | } 14 | -------------------------------------------------------------------------------- /utils/uid/uuid.go: -------------------------------------------------------------------------------- 1 | package uid 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "github.com/satori/go.uuid" 7 | ) 8 | 9 | func NewId() string { 10 | id := uuid.NewV4() 11 | b64 := base64.URLEncoding.EncodeToString(id.Bytes()[:12]) 12 | return b64 13 | } 14 | --------------------------------------------------------------------------------