├── .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 | [](https://github.com/gwuhaolin/livego/actions/workflows/test.yml)
8 | [](https://github.com/gwuhaolin/livego/actions/workflows/release.yml)
9 | [](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 | [](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest)
6 | [](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 |
--------------------------------------------------------------------------------