Repository: gwuhaolin/livego
Branch: master
Commit: 16c6af5d9031
Files: 79
Total size: 272.9 KB
Directory structure:
gitextract_je20vge0/
├── .github/
│ └── workflows/
│ ├── codeql.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── README_cn.md
├── SECURITY.md
├── av/
│ ├── av.go
│ └── rwbase.go
├── configure/
│ ├── channel.go
│ └── liveconfig.go
├── container/
│ ├── flv/
│ │ ├── demuxer.go
│ │ ├── muxer.go
│ │ └── tag.go
│ └── ts/
│ ├── crc32.go
│ ├── muxer.go
│ └── muxer_test.go
├── go.mod
├── go.sum
├── livego.yaml
├── main.go
├── parser/
│ ├── aac/
│ │ └── parser.go
│ ├── h264/
│ │ ├── parser.go
│ │ └── parser_test.go
│ ├── mp3/
│ │ └── parser.go
│ └── parser.go
├── protocol/
│ ├── amf/
│ │ ├── amf.go
│ │ ├── amf_test.go
│ │ ├── const.go
│ │ ├── decoder_amf0.go
│ │ ├── decoder_amf0_test.go
│ │ ├── decoder_amf3.go
│ │ ├── decoder_amf3_external.go
│ │ ├── decoder_amf3_test.go
│ │ ├── encoder_amf0.go
│ │ ├── encoder_amf0_test.go
│ │ ├── encoder_amf3.go
│ │ ├── encoder_amf3_test.go
│ │ ├── metadata.go
│ │ └── util.go
│ ├── api/
│ │ └── api.go
│ ├── hls/
│ │ ├── align.go
│ │ ├── audio_cache.go
│ │ ├── cache.go
│ │ ├── hls.go
│ │ ├── item.go
│ │ ├── source.go
│ │ └── status.go
│ ├── httpflv/
│ │ ├── server.go
│ │ └── writer.go
│ └── rtmp/
│ ├── cache/
│ │ ├── cache.go
│ │ ├── gop.go
│ │ └── special.go
│ ├── core/
│ │ ├── chunk_stream.go
│ │ ├── chunk_stream_test.go
│ │ ├── conn.go
│ │ ├── conn_client.go
│ │ ├── conn_server.go
│ │ ├── conn_test.go
│ │ ├── handshake.go
│ │ ├── read_writer.go
│ │ └── read_writer_test.go
│ ├── rtmp.go
│ ├── rtmprelay/
│ │ ├── rtmprelay.go
│ │ └── staticrelay.go
│ └── stream.go
├── test.go
└── utils/
├── pio/
│ ├── pio.go
│ ├── reader.go
│ └── writer.go
├── pool/
│ └── pool.go
├── queue/
│ └── queue.go
└── uid/
├── rand.go
└── uuid.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/codeql.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '30 23 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: go
build-mode: autobuild
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
release:
types: [published]
jobs:
goreleaser:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '>=1.18.0'
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Get dependencies
run: go get
- name: Go release
uses: goreleaser/goreleaser-action@v1
- name: Docker release
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: gwuhaolin/livego
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push]
jobs:
test:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '>=1.18.0'
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Get dependencies
run: go get
- name: Test
run: go test ./...
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
.idea
dist
.vscode
tmp
vendor
livego
livego.exe
================================================
FILE: .goreleaser.yml
================================================
before:
hooks:
- go mod tidy
builds:
- binary: livego
id: livego
main: ./main.go
goos:
- windows
- darwin
- linux
- freebsd
goarch:
- amd64
- 386
- arm
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- JSON Web Token support.
``` json
// livego.json
{
"jwt": {
"secret": "testing",
"algorithm": "HS256"
},
"server": [
{
"appname": "live",
"live": true,
"hls": true
}
]
}
```
- Use redis for store room keys
``` json
// livego.json
{
"redis_addr": "localhost:6379",
"server": [
{
"appname": "live",
"live": true,
"hls": true
}
]
}
```
- Makefile
### Changed
- Show `players`.
- Show `stream_id`.
- Deleted keys saved in physical file, now the keys are in cached using `go-cache` by default.
- Using `logrus` like log system.
- Using method `.Get(queryParamName)` to get an url query param.
- Replaced `errors.New(...)` to `fmt.Errorf(...)`.
- Replaced types string on config params `liveon` and `hlson` to booleans `live: true/false` and `hls: true/false`
- Using viper for config, allow use file, cloud providers, environment vars or flags.
- Using yaml config by default.
================================================
FILE: Dockerfile
================================================
FROM golang:latest as builder
WORKDIR /app
ENV GOPROXY https://goproxy.io
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego .
FROM alpine:latest
RUN mkdir -p /app/config
WORKDIR /app
ENV RTMP_PORT 1935
ENV HTTP_FLV_PORT 7001
ENV HLS_PORT 7002
ENV HTTP_OPERATION_PORT 8090
COPY --from=builder /app/livego .
EXPOSE ${RTMP_PORT}
EXPOSE ${HTTP_FLV_PORT}
EXPOSE ${HLS_PORT}
EXPOSE ${HTTP_OPERATION_PORT}
ENTRYPOINT ["./livego"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 吴浩麟
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
GOCMD ?= go
GOBUILD = $(GOCMD) build
GOCLEAN = $(GOCMD) clean
GOTEST = $(GOCMD) test
GOGET = $(GOCMD) get
BINARY_NAME = livego
BINARY_UNIX = $(BINARY_NAME)_unix
DOCKER_ACC ?= gwuhaolin
DOCKER_REPO ?= livego
TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
default: all
all: test build dockerize
build:
$(GOBUILD) -o $(BINARY_NAME) -v -ldflags="-X main.VERSION=$(TAG)"
test:
$(GOTEST) -v ./...
clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)
rm -f $(BINARY_UNIX)
run: build
./$(BINARY_NAME)
build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v
dockerize:
docker build -t $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) .
docker push $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG)
================================================
FILE: README.md
================================================
[中文](./README_cn.md)
[](https://github.com/gwuhaolin/livego/actions/workflows/test.yml)
[](https://github.com/gwuhaolin/livego/actions/workflows/release.yml)
[](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml)
Simple and efficient live broadcast server:
- Very simple to install and use;
- Pure Golang, high performance, and cross-platform;
- Supports commonly used transmission protocols, file formats, and encoding formats;
#### Supported transport protocols
- RTMP
- AMF
- HLS
- HTTP-FLV
#### Supported container formats
- FLV
- TS
#### Supported encoding formats
- H264
- AAC
- MP3
## Installation
After directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line.
#### Boot from Docker
Run `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start
#### Compile from source
1. Download the source code `git clone https://github.com/gwuhaolin/livego.git`
2. Go to the livego directory and execute `go build` or `make build`
## Use
1. Start the service: execute the livego binary file or `make run` to start the livego service;
2. Get a channelkey(used for push the video stream) from `http://localhost:8090/control/get?room=movie` and copy data like your channelkey.
3. Upstream push: Push the video stream to `rtmp://localhost:1935/{appname}/{channelkey}` through the` RTMP` protocol(default appname is `live`), for example, use `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` push([download demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv));
4. Downstream playback: The following three playback protocols are supported, and the playback address is as follows:
- `RTMP`:`rtmp://localhost:1935/{appname}/movie`
- `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`
- `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`
5. Use hls via https: generate ssl certificate(server.key, server.crt files), place them in directory with executable file, change "use_hls_https" option in livego.yaml to true (false by default)
all options:
```bash
./livego -h
Usage of ./livego:
--api_addr string HTTP manage interface server listen address (default ":8090")
--config_file string configure filename (default "livego.yaml")
--flv_dir string output flv file at flvDir/APP/KEY_TIME.flv (default "tmp")
--gop_num int gop num (default 1)
--hls_addr string HLS server listen address (default ":7002")
--hls_keep_after_end Maintains the HLS after the stream ends
--httpflv_addr string HTTP-FLV server listen address (default ":7001")
--level string Log level (default "info")
--read_timeout int read time out (default 10)
--rtmp_addr string RTMP server listen address
```
### [Use with flv.js](https://github.com/gwuhaolin/blog/issues/3)
Interested in Golang? Please see [Golang Chinese Learning Materials Summary](http://go.wuhaolin.cn/)
================================================
FILE: README_cn.md
================================================
[](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest)
[](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease)
简单高效的直播服务器:
- 安装和使用非常简单;
- 纯 Golang 编写,性能高,跨平台;
- 支持常用的传输协议、文件格式、编码格式;
#### 支持的传输协议
- RTMP
- AMF
- HLS
- HTTP-FLV
#### 支持的容器格式
- FLV
- TS
#### 支持的编码格式
- H264
- AAC
- MP3
## 安装
直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后,在命令行中执行。
#### 从 Docker 启动
执行`docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego`启动
#### 从源码编译
1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git`
2. 去 livego 目录中 执行 `go build`
## 使用
1. 启动服务:执行 `livego` 二进制文件启动 livego 服务;
2. 访问 `http://localhost:8090/control/get?room=movie` 获取一个房间的 channelkey(channelkey用于推流,movie用于播放).
3. 推流: 通过`RTMP`协议推送视频流到地址 `rtmp://localhost:1935/{appname}/{channelkey}` (appname默认是`live`), 例如: 使用 `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` 推流([下载demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv));
4. 播放: 支持多种播放协议,播放地址如下:
- `RTMP`:`rtmp://localhost:1935/{appname}/movie`
- `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`
- `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`
所有配置项:
```bash
./livego -h
Usage of ./livego:
--api_addr string HTTP管理访问监听地址 (default ":8090")
--config_file string 配置文件路径 (默认 "livego.yaml")
--flv_dir string 输出的 flv 文件路径 flvDir/APP/KEY_TIME.flv (默认 "tmp")
--gop_num int gop 数量 (default 1)
--hls_addr string HLS 服务监听地址 (默认 ":7002")
--hls_keep_after_end Maintains the HLS after the stream ends
--httpflv_addr string HTTP-FLV server listen address (默认 ":7001")
--level string 日志等级 (默认 "info")
--read_timeout int 读超时时间 (默认 10)
--rtmp_addr string RTMP 服务监听地址 (默认 ":1935")
--write_timeout int 写超时时间 (默认 10)
```
### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3)
对Golang感兴趣?请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/)
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.
================================================
FILE: av/av.go
================================================
package av
import (
"fmt"
"io"
)
const (
TAG_AUDIO = 8
TAG_VIDEO = 9
TAG_SCRIPTDATAAMF0 = 18
TAG_SCRIPTDATAAMF3 = 0xf
)
const (
MetadatAMF0 = 0x12
MetadataAMF3 = 0xf
)
const (
SOUND_MP3 = 2
SOUND_NELLYMOSER_16KHZ_MONO = 4
SOUND_NELLYMOSER_8KHZ_MONO = 5
SOUND_NELLYMOSER = 6
SOUND_ALAW = 7
SOUND_MULAW = 8
SOUND_AAC = 10
SOUND_SPEEX = 11
SOUND_5_5Khz = 0
SOUND_11Khz = 1
SOUND_22Khz = 2
SOUND_44Khz = 3
SOUND_8BIT = 0
SOUND_16BIT = 1
SOUND_MONO = 0
SOUND_STEREO = 1
AAC_SEQHDR = 0
AAC_RAW = 1
)
const (
AVC_SEQHDR = 0
AVC_NALU = 1
AVC_EOS = 2
FRAME_KEY = 1
FRAME_INTER = 2
VIDEO_H264 = 7
)
var (
PUBLISH = "publish"
PLAY = "play"
)
// Header can be converted to AudioHeaderInfo or VideoHeaderInfo
type Packet struct {
IsAudio bool
IsVideo bool
IsMetadata bool
TimeStamp uint32 // dts
StreamID uint32
Header PacketHeader
Data []byte
}
type PacketHeader interface {
}
type AudioPacketHeader interface {
PacketHeader
SoundFormat() uint8
AACPacketType() uint8
}
type VideoPacketHeader interface {
PacketHeader
IsKeyFrame() bool
IsSeq() bool
CodecID() uint8
CompositionTime() int32
}
type Demuxer interface {
Demux(*Packet) (ret *Packet, err error)
}
type Muxer interface {
Mux(*Packet, io.Writer) error
}
type SampleRater interface {
SampleRate() (int, error)
}
type CodecParser interface {
SampleRater
Parse(*Packet, io.Writer) error
}
type GetWriter interface {
GetWriter(Info) WriteCloser
}
type Handler interface {
HandleReader(ReadCloser)
HandleWriter(WriteCloser)
}
type Alive interface {
Alive() bool
}
type Closer interface {
Info() Info
Close(error)
}
type CalcTime interface {
CalcBaseTimestamp()
}
type Info struct {
Key string
URL string
UID string
Inter bool
}
func (info Info) IsInterval() bool {
return info.Inter
}
func (info Info) String() string {
return fmt.Sprintf("",
info.Key, info.URL, info.UID, info.Inter)
}
type ReadCloser interface {
Closer
Alive
Read(*Packet) error
}
type WriteCloser interface {
Closer
Alive
CalcTime
Write(*Packet) error
}
================================================
FILE: av/rwbase.go
================================================
package av
import (
"sync"
"time"
)
type RWBaser struct {
lock sync.Mutex
timeout time.Duration
PreTime time.Time
BaseTimestamp uint32
LastVideoTimestamp uint32
LastAudioTimestamp uint32
}
func NewRWBaser(duration time.Duration) RWBaser {
return RWBaser{
timeout: duration,
PreTime: time.Now(),
}
}
func (rw *RWBaser) BaseTimeStamp() uint32 {
return rw.BaseTimestamp
}
func (rw *RWBaser) CalcBaseTimestamp() {
if rw.LastAudioTimestamp > rw.LastVideoTimestamp {
rw.BaseTimestamp = rw.LastAudioTimestamp
} else {
rw.BaseTimestamp = rw.LastVideoTimestamp
}
}
func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) {
if typeID == TAG_VIDEO {
rw.LastVideoTimestamp = timestamp
} else if typeID == TAG_AUDIO {
rw.LastAudioTimestamp = timestamp
}
}
func (rw *RWBaser) SetPreTime() {
rw.lock.Lock()
rw.PreTime = time.Now()
rw.lock.Unlock()
}
func (rw *RWBaser) Alive() bool {
rw.lock.Lock()
b := !(time.Now().Sub(rw.PreTime) >= rw.timeout)
rw.lock.Unlock()
return b
}
================================================
FILE: configure/channel.go
================================================
package configure
import (
"fmt"
"github.com/gwuhaolin/livego/utils/uid"
"github.com/go-redis/redis/v7"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
)
type RoomKeysType struct {
redisCli *redis.Client
localCache *cache.Cache
}
var RoomKeys = &RoomKeysType{
localCache: cache.New(cache.NoExpiration, 0),
}
var saveInLocal = true
func Init() {
saveInLocal = len(Config.GetString("redis_addr")) == 0
if saveInLocal {
return
}
RoomKeys.redisCli = redis.NewClient(&redis.Options{
Addr: Config.GetString("redis_addr"),
Password: Config.GetString("redis_pwd"),
DB: 0,
})
_, err := RoomKeys.redisCli.Ping().Result()
if err != nil {
log.Panic("Redis: ", err)
}
log.Info("Redis connected")
}
// set/reset a random key for channel
func (r *RoomKeysType) SetKey(channel string) (key string, err error) {
if !saveInLocal {
for {
key = uid.RandStringRunes(48)
if _, err = r.redisCli.Get(key).Result(); err == redis.Nil {
err = r.redisCli.Set(channel, key, 0).Err()
if err != nil {
return
}
err = r.redisCli.Set(key, channel, 0).Err()
return
} else if err != nil {
return
}
}
}
for {
key = uid.RandStringRunes(48)
if _, found := r.localCache.Get(key); !found {
r.localCache.SetDefault(channel, key)
r.localCache.SetDefault(key, channel)
break
}
}
return
}
func (r *RoomKeysType) GetKey(channel string) (newKey string, err error) {
if !saveInLocal {
if newKey, err = r.redisCli.Get(channel).Result(); err == redis.Nil {
newKey, err = r.SetKey(channel)
log.Debugf("[KEY] new channel [%s]: %s", channel, newKey)
return
}
return
}
var key interface{}
var found bool
if key, found = r.localCache.Get(channel); found {
return key.(string), nil
}
newKey, err = r.SetKey(channel)
log.Debugf("[KEY] new channel [%s]: %s", channel, newKey)
return
}
func (r *RoomKeysType) GetChannel(key string) (channel string, err error) {
if !saveInLocal {
return r.redisCli.Get(key).Result()
}
chann, found := r.localCache.Get(key)
if found {
return chann.(string), nil
} else {
return "", fmt.Errorf("%s does not exists", key)
}
}
func (r *RoomKeysType) DeleteChannel(channel string) bool {
if !saveInLocal {
return r.redisCli.Del(channel).Err() != nil
}
key, ok := r.localCache.Get(channel)
if ok {
r.localCache.Delete(channel)
r.localCache.Delete(key.(string))
return true
}
return false
}
func (r *RoomKeysType) DeleteKey(key string) bool {
if !saveInLocal {
return r.redisCli.Del(key).Err() != nil
}
channel, ok := r.localCache.Get(key)
if ok {
r.localCache.Delete(channel.(string))
r.localCache.Delete(key)
return true
}
return false
}
================================================
FILE: configure/liveconfig.go
================================================
package configure
import (
"bytes"
"encoding/json"
"strings"
"github.com/kr/pretty"
log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
/*
{
"server": [
{
"appname": "live",
"live": true,
"hls": true,
"static_push": []
}
]
}
*/
type Application struct {
Appname string `mapstructure:"appname"`
Live bool `mapstructure:"live"`
Hls bool `mapstructure:"hls"`
Flv bool `mapstructure:"flv"`
Api bool `mapstructure:"api"`
StaticPush []string `mapstructure:"static_push"`
}
type Applications []Application
type JWT struct {
Secret string `mapstructure:"secret"`
Algorithm string `mapstructure:"algorithm"`
}
type ServerCfg struct {
Level string `mapstructure:"level"`
ConfigFile string `mapstructure:"config_file"`
FLVArchive bool `mapstructure:"flv_archive"`
FLVDir string `mapstructure:"flv_dir"`
RTMPNoAuth bool `mapstructure:"rtmp_noauth"`
RTMPAddr string `mapstructure:"rtmp_addr"`
HTTPFLVAddr string `mapstructure:"httpflv_addr"`
HLSAddr string `mapstructure:"hls_addr"`
HLSKeepAfterEnd bool `mapstructure:"hls_keep_after_end"`
APIAddr string `mapstructure:"api_addr"`
RedisAddr string `mapstructure:"redis_addr"`
RedisPwd string `mapstructure:"redis_pwd"`
ReadTimeout int `mapstructure:"read_timeout"`
WriteTimeout int `mapstructure:"write_timeout"`
EnableTLSVerify bool `mapstructure:"enable_tls_verify"`
GopNum int `mapstructure:"gop_num"`
JWT JWT `mapstructure:"jwt"`
Server Applications `mapstructure:"server"`
}
// default config
var defaultConf = ServerCfg{
ConfigFile: "livego.yaml",
FLVArchive: false,
RTMPNoAuth: false,
RTMPAddr: ":1935",
HTTPFLVAddr: ":7001",
HLSAddr: ":7002",
HLSKeepAfterEnd: false,
APIAddr: ":8090",
WriteTimeout: 10,
ReadTimeout: 10,
EnableTLSVerify: true,
GopNum: 1,
Server: Applications{{
Appname: "live",
Live: true,
Hls: true,
Flv: true,
Api: true,
StaticPush: nil,
}},
}
var (
Config = viper.New()
// BypassInit can be used to bypass the init() function by setting this
// value to True at compile time.
//
// go build -ldflags "-X 'github.com/gwuhaolin/livego/configure.BypassInit=true'" -o livego main.go
BypassInit string = ""
)
func initLog() {
if l, err := log.ParseLevel(Config.GetString("level")); err == nil {
log.SetLevel(l)
log.SetReportCaller(l == log.DebugLevel)
}
}
func init() {
if BypassInit == "" {
initDefault()
}
}
func initDefault() {
defer Init()
// Default config
b, _ := json.Marshal(defaultConf)
defaultConfig := bytes.NewReader(b)
viper.SetConfigType("json")
viper.ReadConfig(defaultConfig)
Config.MergeConfigMap(viper.AllSettings())
// Flags
pflag.String("rtmp_addr", ":1935", "RTMP server listen address")
pflag.Bool("enable_rtmps", false, "enable server session RTMPS")
pflag.String("rtmps_cert", "server.crt", "cert file path required for RTMPS")
pflag.String("rtmps_key", "server.key", "key file path required for RTMPS")
pflag.String("httpflv_addr", ":7001", "HTTP-FLV server listen address")
pflag.String("hls_addr", ":7002", "HLS server listen address")
pflag.String("api_addr", ":8090", "HTTP manage interface server listen address")
pflag.String("config_file", "livego.yaml", "configure filename")
pflag.String("level", "info", "Log level")
pflag.Bool("hls_keep_after_end", false, "Maintains the HLS after the stream ends")
pflag.String("flv_dir", "tmp", "output flv file at flvDir/APP/KEY_TIME.flv")
pflag.Int("read_timeout", 10, "read time out")
pflag.Int("write_timeout", 10, "write time out")
pflag.Int("gop_num", 1, "gop num")
pflag.Bool("enable_tls_verify", true, "Use system root CA to verify RTMPS connection, set this flag to false on Windows")
pflag.Parse()
Config.BindPFlags(pflag.CommandLine)
// File
Config.SetConfigFile(Config.GetString("config_file"))
Config.AddConfigPath(".")
err := Config.ReadInConfig()
if err != nil {
log.Warning(err)
log.Info("Using default config")
} else {
Config.MergeInConfig()
}
// Environment
replacer := strings.NewReplacer(".", "_")
Config.SetEnvKeyReplacer(replacer)
Config.AllowEmptyEnv(true)
Config.AutomaticEnv()
// Log
initLog()
// Print final config
c := ServerCfg{}
Config.Unmarshal(&c)
log.Debugf("Current configurations: \n%# v", pretty.Formatter(c))
}
func CheckAppName(appname string) bool {
apps := Applications{}
Config.UnmarshalKey("server", &apps)
for _, app := range apps {
if app.Appname == appname {
return app.Live
}
}
return false
}
func GetStaticPushUrlList(appname string) ([]string, bool) {
apps := Applications{}
Config.UnmarshalKey("server", &apps)
for _, app := range apps {
if (app.Appname == appname) && app.Live {
if len(app.StaticPush) > 0 {
return app.StaticPush, true
} else {
return nil, false
}
}
}
return nil, false
}
================================================
FILE: container/flv/demuxer.go
================================================
package flv
import (
"fmt"
"github.com/gwuhaolin/livego/av"
)
var (
ErrAvcEndSEQ = fmt.Errorf("avc end sequence")
)
type Demuxer struct {
}
func NewDemuxer() *Demuxer {
return &Demuxer{}
}
func (d *Demuxer) DemuxH(p *av.Packet) error {
var tag Tag
_, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
if err != nil {
return err
}
p.Header = &tag
return nil
}
func (d *Demuxer) Demux(p *av.Packet) error {
var tag Tag
n, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
if err != nil {
return err
}
if tag.CodecID() == av.VIDEO_H264 &&
p.Data[0] == 0x17 && p.Data[1] == 0x02 {
return ErrAvcEndSEQ
}
p.Header = &tag
p.Data = p.Data[n:]
return nil
}
================================================
FILE: container/flv/muxer.go
================================================
package flv
import (
"fmt"
"os"
"path"
"strings"
"time"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/uid"
log "github.com/sirupsen/logrus"
)
var (
flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}
)
/*
func NewFlv(handler av.Handler, info av.Info) {
patths := strings.SplitN(info.Key, "/", 2)
if len(patths) != 2 {
log.Warning("invalid info")
return
}
w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
log.Error("open file error: ", err)
}
writer := NewFLVWriter(patths[0], patths[1], info.URL, w)
handler.HandleWriter(writer)
writer.Wait()
// close flv file
log.Debug("close flv file")
writer.ctx.Close()
}
*/
const (
headerLen = 11
)
type FLVWriter struct {
Uid string
av.RWBaser
app, title, url string
buf []byte
closed chan struct{}
ctx *os.File
closedWriter bool
}
func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter {
ret := &FLVWriter{
Uid: uid.NewId(),
app: app,
title: title,
url: url,
ctx: ctx,
RWBaser: av.NewRWBaser(time.Second * 10),
closed: make(chan struct{}),
buf: make([]byte, headerLen),
}
ret.ctx.Write(flvHeader)
pio.PutI32BE(ret.buf[:4], 0)
ret.ctx.Write(ret.buf[:4])
return ret
}
func (writer *FLVWriter) Write(p *av.Packet) error {
writer.RWBaser.SetPreTime()
h := writer.buf[:headerLen]
typeID := av.TAG_VIDEO
if !p.IsVideo {
if p.IsMetadata {
var err error
typeID = av.TAG_SCRIPTDATAAMF0
p.Data, err = amf.MetaDataReform(p.Data, amf.DEL)
if err != nil {
return err
}
} else {
typeID = av.TAG_AUDIO
}
}
dataLen := len(p.Data)
timestamp := p.TimeStamp
timestamp += writer.BaseTimeStamp()
writer.RWBaser.RecTimeStamp(timestamp, uint32(typeID))
preDataLen := dataLen + headerLen
timestampbase := timestamp & 0xffffff
timestampExt := timestamp >> 24 & 0xff
pio.PutU8(h[0:1], uint8(typeID))
pio.PutI24BE(h[1:4], int32(dataLen))
pio.PutI24BE(h[4:7], int32(timestampbase))
pio.PutU8(h[7:8], uint8(timestampExt))
if _, err := writer.ctx.Write(h); err != nil {
return err
}
if _, err := writer.ctx.Write(p.Data); err != nil {
return err
}
pio.PutI32BE(h[:4], int32(preDataLen))
if _, err := writer.ctx.Write(h[:4]); err != nil {
return err
}
return nil
}
func (writer *FLVWriter) Wait() {
select {
case <-writer.closed:
return
}
}
func (writer *FLVWriter) Close(error) {
if writer.closedWriter {
return
}
writer.closedWriter = true
writer.ctx.Close()
close(writer.closed)
}
func (writer *FLVWriter) Info() (ret av.Info) {
ret.UID = writer.Uid
ret.URL = writer.url
ret.Key = writer.app + "/" + writer.title
return
}
type FlvDvr struct{}
func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser {
paths := strings.SplitN(info.Key, "/", 2)
if len(paths) != 2 {
log.Warning("invalid info")
return nil
}
flvDir := configure.Config.GetString("flv_dir")
err := os.MkdirAll(path.Join(flvDir, paths[0]), 0755)
if err != nil {
log.Error("mkdir error: ", err)
return nil
}
fileName := fmt.Sprintf("%s_%d.%s", path.Join(flvDir, info.Key), time.Now().Unix(), "flv")
log.Debug("flv dvr save stream to: ", fileName)
w, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
log.Error("open file error: ", err)
return nil
}
writer := NewFLVWriter(paths[0], paths[1], info.URL, w)
log.Debug("new flv dvr: ", writer.Info())
return writer
}
================================================
FILE: container/flv/tag.go
================================================
package flv
import (
"fmt"
"github.com/gwuhaolin/livego/av"
)
type flvTag struct {
fType uint8
dataSize uint32
timeStamp uint32
streamID uint32 // always 0
}
type mediaTag struct {
/*
SoundFormat: UB[4]
0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
11 = Speex
14 = MP3 8-Khz
15 = Device-specific sound
Formats 7, 8, 14, and 15 are reserved for internal use
AAC is supported in Flash Player 9,0,115,0 and higher.
Speex is supported in Flash Player 10 and higher.
*/
soundFormat uint8
/*
SoundRate: UB[2]
Sampling rate
0 = 5.5-kHz For AAC: always 3
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
*/
soundRate uint8
/*
SoundSize: UB[1]
0 = snd8Bit
1 = snd16Bit
Size of each sample.
This parameter only pertains to uncompressed formats.
Compressed formats always decode to 16 bits internally
*/
soundSize uint8
/*
SoundType: UB[1]
0 = sndMono
1 = sndStereo
Mono or stereo sound For Nellymoser: always 0
For AAC: always 1
*/
soundType uint8
/*
0: AAC sequence header
1: AAC raw
*/
aacPacketType uint8
/*
1: keyframe (for AVC, a seekable frame)
2: inter frame (for AVC, a non- seekable frame)
3: disposable inter frame (H.263 only)
4: generated keyframe (reserved for server use only)
5: video info/command frame
*/
frameType uint8
/*
1: JPEG (currently unused)
2: Sorenson H.263
3: Screen video
4: On2 VP6
5: On2 VP6 with alpha channel
6: Screen video version 2
7: AVC
*/
codecID uint8
/*
0: AVC sequence header
1: AVC NALU
2: AVC end of sequence (lower level NALU sequence ender is not required or supported)
*/
avcPacketType uint8
compositionTime int32
}
type Tag struct {
flvt flvTag
mediat mediaTag
}
func (tag *Tag) SoundFormat() uint8 {
return tag.mediat.soundFormat
}
func (tag *Tag) AACPacketType() uint8 {
return tag.mediat.aacPacketType
}
func (tag *Tag) IsKeyFrame() bool {
return tag.mediat.frameType == av.FRAME_KEY
}
func (tag *Tag) IsSeq() bool {
return tag.mediat.frameType == av.FRAME_KEY &&
tag.mediat.avcPacketType == av.AVC_SEQHDR
}
func (tag *Tag) CodecID() uint8 {
return tag.mediat.codecID
}
func (tag *Tag) CompositionTime() int32 {
return tag.mediat.compositionTime
}
// ParseMediaTagHeader, parse video, audio, tag header
func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) {
switch isVideo {
case false:
n, err = tag.parseAudioHeader(b)
case true:
n, err = tag.parseVideoHeader(b)
}
return
}
func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) {
if len(b) < n+1 {
err = fmt.Errorf("invalid audiodata len=%d", len(b))
return
}
flags := b[0]
tag.mediat.soundFormat = flags >> 4
tag.mediat.soundRate = (flags >> 2) & 0x3
tag.mediat.soundSize = (flags >> 1) & 0x1
tag.mediat.soundType = flags & 0x1
n++
switch tag.mediat.soundFormat {
case av.SOUND_AAC:
tag.mediat.aacPacketType = b[1]
n++
}
return
}
func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) {
if len(b) < n+5 {
err = fmt.Errorf("invalid videodata len=%d", len(b))
return
}
flags := b[0]
tag.mediat.frameType = flags >> 4
tag.mediat.codecID = flags & 0xf
n++
if tag.mediat.frameType == av.FRAME_INTER || tag.mediat.frameType == av.FRAME_KEY {
tag.mediat.avcPacketType = b[1]
for i := 2; i < 5; i++ {
tag.mediat.compositionTime = tag.mediat.compositionTime<<8 + int32(b[i])
}
n += 4
}
return
}
================================================
FILE: container/ts/crc32.go
================================================
package ts
func GenCrc32(src []byte) uint32 {
crcTable := []uint32{
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}
j := byte(0)
crc32 := uint32(0xFFFFFFFF)
for i := 0; i < len(src); i++ {
j = (byte(crc32>>24) ^ src[i]) & 0xff
crc32 = uint32(uint32(crc32<<8) ^ uint32(crcTable[j]))
}
return crc32
}
================================================
FILE: container/ts/muxer.go
================================================
package ts
import (
"io"
"github.com/gwuhaolin/livego/av"
)
const (
tsDefaultDataLen = 184
tsPacketLen = 188
h264DefaultHZ = 90
videoPID = 0x100
audioPID = 0x101
videoSID = 0xe0
audioSID = 0xc0
)
type Muxer struct {
videoCc byte
audioCc byte
patCc byte
pmtCc byte
pat [tsPacketLen]byte
pmt [tsPacketLen]byte
tsPacket [tsPacketLen]byte
}
func NewMuxer() *Muxer {
return &Muxer{}
}
func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error {
first := true
wBytes := 0
pesIndex := 0
tmpLen := byte(0)
dataLen := byte(0)
var pes pesHeader
dts := int64(p.TimeStamp) * int64(h264DefaultHZ)
pts := dts
pid := audioPID
var videoH av.VideoPacketHeader
if p.IsVideo {
pid = videoPID
videoH, _ = p.Header.(av.VideoPacketHeader)
pts = dts + int64(videoH.CompositionTime())*int64(h264DefaultHZ)
}
err := pes.packet(p, pts, dts)
if err != nil {
return err
}
pesHeaderLen := pes.len
packetBytesLen := len(p.Data) + int(pesHeaderLen)
for {
if packetBytesLen <= 0 {
break
}
if p.IsVideo {
muxer.videoCc++
if muxer.videoCc > 0xf {
muxer.videoCc = 0
}
} else {
muxer.audioCc++
if muxer.audioCc > 0xf {
muxer.audioCc = 0
}
}
i := byte(0)
//sync byte
muxer.tsPacket[i] = 0x47
i++
//error indicator, unit start indicator,ts priority,pid
muxer.tsPacket[i] = byte(pid >> 8) //pid high 5 bits
if first {
muxer.tsPacket[i] = muxer.tsPacket[i] | 0x40 //unit start indicator
}
i++
//pid low 8 bits
muxer.tsPacket[i] = byte(pid)
i++
//scram control, adaptation control, counter
if p.IsVideo {
muxer.tsPacket[i] = 0x10 | byte(muxer.videoCc&0x0f)
} else {
muxer.tsPacket[i] = 0x10 | byte(muxer.audioCc&0x0f)
}
i++
//关键帧需要加pcr
if first && p.IsVideo && videoH.IsKeyFrame() {
muxer.tsPacket[3] |= 0x20
muxer.tsPacket[i] = 7
i++
muxer.tsPacket[i] = 0x50
i++
muxer.writePcr(muxer.tsPacket[0:], i, dts)
i += 6
}
//frame data
if packetBytesLen >= tsDefaultDataLen {
dataLen = tsDefaultDataLen
if first {
dataLen -= (i - 4)
}
} else {
muxer.tsPacket[3] |= 0x20 //have adaptation
remainBytes := byte(0)
dataLen = byte(packetBytesLen)
if first {
remainBytes = tsDefaultDataLen - dataLen - (i - 4)
} else {
remainBytes = tsDefaultDataLen - dataLen
}
muxer.adaptationBufInit(muxer.tsPacket[i:], byte(remainBytes))
i += remainBytes
}
if first && i < tsPacketLen && pesHeaderLen > 0 {
tmpLen = tsPacketLen - i
if pesHeaderLen <= tmpLen {
tmpLen = pesHeaderLen
}
copy(muxer.tsPacket[i:], pes.data[pesIndex:pesIndex+int(tmpLen)])
i += tmpLen
packetBytesLen -= int(tmpLen)
dataLen -= tmpLen
pesHeaderLen -= tmpLen
pesIndex += int(tmpLen)
}
if i < tsPacketLen {
tmpLen = tsPacketLen - i
if tmpLen <= dataLen {
dataLen = tmpLen
}
copy(muxer.tsPacket[i:], p.Data[wBytes:wBytes+int(dataLen)])
wBytes += int(dataLen)
packetBytesLen -= int(dataLen)
}
if w != nil {
if _, err := w.Write(muxer.tsPacket[0:]); err != nil {
return err
}
}
first = false
}
return nil
}
//PAT return pat data
func (muxer *Muxer) PAT() []byte {
i := 0
remainByte := 0
tsHeader := []byte{0x47, 0x40, 0x00, 0x10, 0x00}
patHeader := []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x01}
if muxer.patCc > 0xf {
muxer.patCc = 0
}
tsHeader[3] |= muxer.patCc & 0x0f
muxer.patCc++
copy(muxer.pat[i:], tsHeader)
i += len(tsHeader)
copy(muxer.pat[i:], patHeader)
i += len(patHeader)
crc32Value := GenCrc32(patHeader)
muxer.pat[i] = byte(crc32Value >> 24)
i++
muxer.pat[i] = byte(crc32Value >> 16)
i++
muxer.pat[i] = byte(crc32Value >> 8)
i++
muxer.pat[i] = byte(crc32Value)
i++
remainByte = int(tsPacketLen - i)
for j := 0; j < remainByte; j++ {
muxer.pat[i+j] = 0xff
}
return muxer.pat[0:]
}
// PMT return pmt data
func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte {
i := int(0)
j := int(0)
var progInfo []byte
remainBytes := int(0)
tsHeader := []byte{0x47, 0x50, 0x01, 0x10, 0x00}
pmtHeader := []byte{0x02, 0xb0, 0xff, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00}
if !hasVideo {
pmtHeader[9] = 0x01
progInfo = []byte{0x0f, 0xe1, 0x01, 0xf0, 0x00}
} else {
progInfo = []byte{0x1b, 0xe1, 0x00, 0xf0, 0x00, //h264 or h265*
0x0f, 0xe1, 0x01, 0xf0, 0x00, //mp3 or aac
}
}
pmtHeader[2] = byte(len(progInfo) + 9 + 4)
if muxer.pmtCc > 0xf {
muxer.pmtCc = 0
}
tsHeader[3] |= muxer.pmtCc & 0x0f
muxer.pmtCc++
if soundFormat == 2 ||
soundFormat == 14 {
if hasVideo {
progInfo[5] = 0x4
} else {
progInfo[0] = 0x4
}
}
copy(muxer.pmt[i:], tsHeader)
i += len(tsHeader)
copy(muxer.pmt[i:], pmtHeader)
i += len(pmtHeader)
copy(muxer.pmt[i:], progInfo[0:])
i += len(progInfo)
crc32Value := GenCrc32(muxer.pmt[5 : 5+len(pmtHeader)+len(progInfo)])
muxer.pmt[i] = byte(crc32Value >> 24)
i++
muxer.pmt[i] = byte(crc32Value >> 16)
i++
muxer.pmt[i] = byte(crc32Value >> 8)
i++
muxer.pmt[i] = byte(crc32Value)
i++
remainBytes = int(tsPacketLen - i)
for j = 0; j < remainBytes; j++ {
muxer.pmt[i+j] = 0xff
}
return muxer.pmt[0:]
}
func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) {
src[0] = byte(remainBytes - 1)
if remainBytes == 1 {
} else {
src[1] = 0x00
for i := 2; i < len(src); i++ {
src[i] = 0xff
}
}
return
}
func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error {
b[i] = byte(pcr >> 25)
i++
b[i] = byte((pcr >> 17) & 0xff)
i++
b[i] = byte((pcr >> 9) & 0xff)
i++
b[i] = byte((pcr >> 1) & 0xff)
i++
b[i] = byte(((pcr & 0x1) << 7) | 0x7e)
i++
b[i] = 0x00
return nil
}
type pesHeader struct {
len byte
data [tsPacketLen]byte
}
// packet return pes packet
func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error {
//PES header
i := 0
header.data[i] = 0x00
i++
header.data[i] = 0x00
i++
header.data[i] = 0x01
i++
sid := audioSID
if p.IsVideo {
sid = videoSID
}
header.data[i] = byte(sid)
i++
flag := 0x80
ptslen := 5
dtslen := ptslen
headerSize := ptslen
if p.IsVideo && pts != dts {
flag |= 0x40
headerSize += 5 //add dts
}
size := len(p.Data) + headerSize + 3
if size > 0xffff {
size = 0
}
header.data[i] = byte(size >> 8)
i++
header.data[i] = byte(size)
i++
header.data[i] = 0x80
i++
header.data[i] = byte(flag)
i++
header.data[i] = byte(headerSize)
i++
header.writeTs(header.data[0:], i, flag>>6, pts)
i += ptslen
if p.IsVideo && pts != dts {
header.writeTs(header.data[0:], i, 1, dts)
i += dtslen
}
header.len = byte(i)
return nil
}
func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) {
val := uint32(0)
if ts > 0x1ffffffff {
ts -= 0x1ffffffff
}
val = uint32(fb<<4) | ((uint32(ts>>30) & 0x07) << 1) | 1
src[i] = byte(val)
i++
val = ((uint32(ts>>15) & 0x7fff) << 1) | 1
src[i] = byte(val >> 8)
i++
src[i] = byte(val)
i++
val = (uint32(ts&0x7fff) << 1) | 1
src[i] = byte(val >> 8)
i++
src[i] = byte(val)
}
================================================
FILE: container/ts/muxer_test.go
================================================
package ts
import (
"testing"
"github.com/gwuhaolin/livego/av"
"github.com/stretchr/testify/assert"
)
type TestWriter struct {
buf []byte
count int
}
//Write write p to w.buf
func (w *TestWriter) Write(p []byte) (int, error) {
w.count++
w.buf = p
return len(p), nil
}
func TestTSEncoder(t *testing.T) {
at := assert.New(t)
m := NewMuxer()
w := &TestWriter{}
data := []byte{0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81,
0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7,
0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38,
}
p := av.Packet{
IsVideo: false,
Data: data,
}
err := m.Mux(&p, w)
at.Equal(err, nil)
at.Equal(w.count, 1)
at.Equal(w.buf, []byte{0x47, 0x41, 0x01, 0x31, 0x81, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x30,
0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01, 0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d,
0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04,
0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00,
0x06, 0x00, 0x38})
}
================================================
FILE: go.mod
================================================
module github.com/gwuhaolin/livego
go 1.13
require (
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-redis/redis/v7 v7.2.0
github.com/gorilla/mux v1.7.4 // indirect
github.com/kr/pretty v0.1.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.5.0
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.6.3
github.com/stretchr/testify v1.4.0
github.com/urfave/negroni v1.0.0 // indirect
)
================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw=
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
================================================
FILE: livego.yaml
================================================
# # Logger level
# level: info
# # FLV Options
# flv_archive: false
# flv_dir: "./tmp"
# httpflv_addr: ":7001"
# # RTMP Options
# rtmp_noauth: false
# rtmp_addr: ":1935"
# enable_rtmps: true
# rtmps_cert: server.crt
# rtmps_key: server.key
# read_timeout: 10
# write_timeout: 10
# # HLS Options
# hls_addr: ":7002"
#use_hls_https: true
# # API Options
# api_addr: ":8090"
server:
- appname: live
live: true
hls: true
api: true
flv: true
================================================
FILE: main.go
================================================
package main
import (
"crypto/tls"
"fmt"
"net"
"path"
"runtime"
"time"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/api"
"github.com/gwuhaolin/livego/protocol/hls"
"github.com/gwuhaolin/livego/protocol/httpflv"
"github.com/gwuhaolin/livego/protocol/rtmp"
log "github.com/sirupsen/logrus"
)
var VERSION = "master"
func startHls() *hls.Server {
hlsAddr := configure.Config.GetString("hls_addr")
hlsListen, err := net.Listen("tcp", hlsAddr)
if err != nil {
log.Fatal(err)
}
hlsServer := hls.NewServer()
go func() {
defer func() {
if r := recover(); r != nil {
log.Error("HLS server panic: ", r)
}
}()
log.Info("HLS listen On ", hlsAddr)
hlsServer.Serve(hlsListen)
}()
return hlsServer
}
func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) {
rtmpAddr := configure.Config.GetString("rtmp_addr")
isRtmps := configure.Config.GetBool("enable_rtmps")
var rtmpListen net.Listener
if isRtmps {
certPath := configure.Config.GetString("rtmps_cert")
keyPath := configure.Config.GetString("rtmps_key")
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
log.Fatal(err)
}
rtmpListen, err = tls.Listen("tcp", rtmpAddr, &tls.Config{
Certificates: []tls.Certificate{cert},
})
if err != nil {
log.Fatal(err)
}
} else {
var err error
rtmpListen, err = net.Listen("tcp", rtmpAddr)
if err != nil {
log.Fatal(err)
}
}
var rtmpServer *rtmp.Server
if hlsServer == nil {
rtmpServer = rtmp.NewRtmpServer(stream, nil)
log.Info("HLS server disable....")
} else {
rtmpServer = rtmp.NewRtmpServer(stream, hlsServer)
log.Info("HLS server enable....")
}
defer func() {
if r := recover(); r != nil {
log.Error("RTMP server panic: ", r)
}
}()
if isRtmps {
log.Info("RTMPS Listen On ", rtmpAddr)
} else {
log.Info("RTMP Listen On ", rtmpAddr)
}
rtmpServer.Serve(rtmpListen)
}
func startHTTPFlv(stream *rtmp.RtmpStream) {
httpflvAddr := configure.Config.GetString("httpflv_addr")
flvListen, err := net.Listen("tcp", httpflvAddr)
if err != nil {
log.Fatal(err)
}
hdlServer := httpflv.NewServer(stream)
go func() {
defer func() {
if r := recover(); r != nil {
log.Error("HTTP-FLV server panic: ", r)
}
}()
log.Info("HTTP-FLV listen On ", httpflvAddr)
hdlServer.Serve(flvListen)
}()
}
func startAPI(stream *rtmp.RtmpStream) {
apiAddr := configure.Config.GetString("api_addr")
rtmpAddr := configure.Config.GetString("rtmp_addr")
if apiAddr != "" {
opListen, err := net.Listen("tcp", apiAddr)
if err != nil {
log.Fatal(err)
}
opServer := api.NewServer(stream, rtmpAddr)
go func() {
defer func() {
if r := recover(); r != nil {
log.Error("HTTP-API server panic: ", r)
}
}()
log.Info("HTTP-API listen On ", apiAddr)
opServer.Serve(opListen)
}()
}
}
func init() {
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
filename := path.Base(f.File)
return fmt.Sprintf("%s()", f.Function), fmt.Sprintf(" %s:%d", filename, f.Line)
},
})
}
func main() {
defer func() {
if r := recover(); r != nil {
log.Error("livego panic: ", r)
time.Sleep(1 * time.Second)
}
}()
log.Infof(`
_ _ ____
| | (_)_ _____ / ___| ___
| | | \ \ / / _ \ | _ / _ \
| |___| |\ V / __/ |_| | (_) |
|_____|_| \_/ \___|\____|\___/
version: %s
`, VERSION)
apps := configure.Applications{}
configure.Config.UnmarshalKey("server", &apps)
for _, app := range apps {
stream := rtmp.NewRtmpStream()
var hlsServer *hls.Server
if app.Hls {
hlsServer = startHls()
}
if app.Flv {
startHTTPFlv(stream)
}
if app.Api {
startAPI(stream)
}
startRtmp(stream, hlsServer)
}
}
================================================
FILE: parser/aac/parser.go
================================================
package aac
import (
"fmt"
"io"
"github.com/gwuhaolin/livego/av"
)
type mpegExtension struct {
objectType byte
sampleRate byte
}
type mpegCfgInfo struct {
objectType byte
sampleRate byte
channel byte
sbr byte
ps byte
frameLen byte
exceptionLogTs int64
extension *mpegExtension
}
var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}
var (
specificBufInvalid = fmt.Errorf("audio mpegspecific error")
audioBufInvalid = fmt.Errorf("audiodata invalid")
)
const (
adtsHeaderLen = 7
)
type Parser struct {
gettedSpecific bool
adtsHeader []byte
cfgInfo *mpegCfgInfo
}
func NewParser() *Parser {
return &Parser{
gettedSpecific: false,
cfgInfo: &mpegCfgInfo{},
adtsHeader: make([]byte, adtsHeaderLen),
}
}
func (parser *Parser) specificInfo(src []byte) error {
if len(src) < 2 {
return specificBufInvalid
}
parser.gettedSpecific = true
parser.cfgInfo.objectType = (src[0] >> 3) & 0xff
parser.cfgInfo.sampleRate = ((src[0] & 0x07) << 1) | src[1]>>7
parser.cfgInfo.channel = (src[1] >> 3) & 0x0f
return nil
}
func (parser *Parser) adts(src []byte, w io.Writer) error {
if len(src) <= 0 || !parser.gettedSpecific {
return audioBufInvalid
}
frameLen := uint16(len(src)) + 7
//first write adts header
parser.adtsHeader[0] = 0xff
parser.adtsHeader[1] = 0xf1
parser.adtsHeader[2] &= 0x00
parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.objectType-1)<<6
parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.sampleRate)<<2
parser.adtsHeader[3] &= 0x00
parser.adtsHeader[3] = parser.adtsHeader[3] | (parser.cfgInfo.channel<<2)<<4
parser.adtsHeader[3] = parser.adtsHeader[3] | byte((frameLen<<3)>>14)
parser.adtsHeader[4] &= 0x00
parser.adtsHeader[4] = parser.adtsHeader[4] | byte((frameLen<<5)>>8)
parser.adtsHeader[5] &= 0x00
parser.adtsHeader[5] = parser.adtsHeader[5] | byte(((frameLen<<13)>>13)<<5)
parser.adtsHeader[5] = parser.adtsHeader[5] | (0x7C<<1)>>3
parser.adtsHeader[6] = 0xfc
if _, err := w.Write(parser.adtsHeader[0:]); err != nil {
return err
}
if _, err := w.Write(src); err != nil {
return err
}
return nil
}
func (parser *Parser) SampleRate() int {
rate := 44100
if parser.cfgInfo.sampleRate <= byte(len(aacRates)-1) {
rate = aacRates[parser.cfgInfo.sampleRate]
}
return rate
}
func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (err error) {
switch packetType {
case av.AAC_SEQHDR:
err = parser.specificInfo(b)
case av.AAC_RAW:
err = parser.adts(b, w)
}
return
}
================================================
FILE: parser/h264/parser.go
================================================
package h264
import (
"bytes"
"fmt"
"io"
)
const (
i_frame byte = 0
p_frame byte = 1
b_frame byte = 2
)
const (
nalu_type_not_define byte = 0
nalu_type_slice byte = 1 //slice_layer_without_partioning_rbsp() sliceheader
nalu_type_dpa byte = 2 // slice_data_partition_a_layer_rbsp( ), slice_header
nalu_type_dpb byte = 3 // slice_data_partition_b_layer_rbsp( )
nalu_type_dpc byte = 4 // slice_data_partition_c_layer_rbsp( )
nalu_type_idr byte = 5 // slice_layer_without_partitioning_rbsp( ),sliceheader
nalu_type_sei byte = 6 //sei_rbsp( )
nalu_type_sps byte = 7 //seq_parameter_set_rbsp( )
nalu_type_pps byte = 8 //pic_parameter_set_rbsp( )
nalu_type_aud byte = 9 // access_unit_delimiter_rbsp( )
nalu_type_eoesq byte = 10 //end_of_seq_rbsp( )
nalu_type_eostream byte = 11 //end_of_stream_rbsp( )
nalu_type_filler byte = 12 //filler_data_rbsp( )
)
const (
naluBytesLen int = 4
maxSpsPpsLen int = 2 * 1024
)
var (
decDataNil = fmt.Errorf("dec buf is nil")
spsDataError = fmt.Errorf("sps data error")
ppsHeaderError = fmt.Errorf("pps header error")
ppsDataError = fmt.Errorf("pps data error")
naluHeaderInvalid = fmt.Errorf("nalu header invalid")
videoDataInvalid = fmt.Errorf("video data not match")
dataSizeNotMatch = fmt.Errorf("data size not match")
naluBodyLenError = fmt.Errorf("nalu body len error")
)
var startCode = []byte{0x00, 0x00, 0x00, 0x01}
var naluAud = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0}
type Parser struct {
frameType byte
specificInfo []byte
pps *bytes.Buffer
}
type sequenceHeader struct {
configVersion byte //8bits
avcProfileIndication byte //8bits
profileCompatility byte //8bits
avcLevelIndication byte //8bits
reserved1 byte //6bits
naluLen byte //2bits
reserved2 byte //3bits
spsNum byte //5bits
ppsNum byte //8bits
spsLen int
ppsLen int
}
func NewParser() *Parser {
return &Parser{
pps: bytes.NewBuffer(make([]byte, maxSpsPpsLen)),
}
}
//return value 1:sps, value2 :pps
func (parser *Parser) parseSpecificInfo(src []byte) error {
if len(src) < 9 {
return decDataNil
}
sps := []byte{}
pps := []byte{}
var seq sequenceHeader
seq.configVersion = src[0]
seq.avcProfileIndication = src[1]
seq.profileCompatility = src[2]
seq.avcLevelIndication = src[3]
seq.reserved1 = src[4] & 0xfc
seq.naluLen = src[4]&0x03 + 1
seq.reserved2 = src[5] >> 5
//get sps
seq.spsNum = src[5] & 0x1f
seq.spsLen = int(src[6])<<8 | int(src[7])
if len(src[8:]) < seq.spsLen || seq.spsLen <= 0 {
return spsDataError
}
sps = append(sps, startCode...)
sps = append(sps, src[8:(8+seq.spsLen)]...)
//get pps
tmpBuf := src[(8 + seq.spsLen):]
if len(tmpBuf) < 4 {
return ppsHeaderError
}
seq.ppsNum = tmpBuf[0]
seq.ppsLen = int(0)<<16 | int(tmpBuf[1])<<8 | int(tmpBuf[2])
if len(tmpBuf[3:]) < seq.ppsLen || seq.ppsLen <= 0 {
return ppsDataError
}
pps = append(pps, startCode...)
pps = append(pps, tmpBuf[3:]...)
parser.specificInfo = append(parser.specificInfo, sps...)
parser.specificInfo = append(parser.specificInfo, pps...)
return nil
}
func (parser *Parser) isNaluHeader(src []byte) bool {
if len(src) < naluBytesLen {
return false
}
return src[0] == 0x00 &&
src[1] == 0x00 &&
src[2] == 0x00 &&
src[3] == 0x01
}
func (parser *Parser) naluSize(src []byte) (int, error) {
if len(src) < naluBytesLen {
return 0, fmt.Errorf("nalusizedata invalid")
}
buf := src[:naluBytesLen]
size := int(0)
for i := 0; i < len(buf); i++ {
size = size<<8 + int(buf[i])
}
return size, nil
}
func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error {
dataSize := len(src)
if dataSize < naluBytesLen {
return videoDataInvalid
}
parser.pps.Reset()
_, err := w.Write(naluAud)
if err != nil {
return err
}
index := 0
nalLen := 0
hasSpsPps := false
hasWriteSpsPps := false
for dataSize > 0 {
nalLen, err = parser.naluSize(src[index:])
if err != nil {
return dataSizeNotMatch
}
index += naluBytesLen
dataSize -= naluBytesLen
if dataSize >= nalLen && len(src[index:]) >= nalLen && nalLen > 0 {
nalType := src[index] & 0x1f
switch nalType {
case nalu_type_aud:
case nalu_type_idr:
if !hasWriteSpsPps {
hasWriteSpsPps = true
if !hasSpsPps {
if _, err := w.Write(parser.specificInfo); err != nil {
return err
}
} else {
if _, err := w.Write(parser.pps.Bytes()); err != nil {
return err
}
}
}
fallthrough
case nalu_type_slice:
fallthrough
case nalu_type_sei:
_, err := w.Write(startCode)
if err != nil {
return err
}
_, err = w.Write(src[index : index+nalLen])
if err != nil {
return err
}
case nalu_type_sps:
fallthrough
case nalu_type_pps:
hasSpsPps = true
_, err := parser.pps.Write(startCode)
if err != nil {
return err
}
_, err = parser.pps.Write(src[index : index+nalLen])
if err != nil {
return err
}
}
index += nalLen
dataSize -= nalLen
} else {
return naluBodyLenError
}
}
return nil
}
func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err error) {
switch isSeq {
case true:
err = parser.parseSpecificInfo(b)
case false:
// is annexb
if parser.isNaluHeader(b) {
_, err = w.Write(b)
} else {
err = parser.getAnnexbH264(b, w)
}
}
return
}
================================================
FILE: parser/h264/parser_test.go
================================================
package h264
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestH264SeqDemux(t *testing.T) {
at := assert.New(t)
seq := []byte{
0x01, 0x4d, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x4d, 0x00,
0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,
0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x01, 0x00,
0x04, 0x68, 0xde, 0x31, 0x12,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(seq, true, w)
at.Equal(err, nil)
at.Equal(d.specificInfo, []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00,
0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,
0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12})
}
func TestH264AnnexbDemux(t *testing.T) {
at := assert.New(t)
nalu := []byte{
0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,
0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w)
at.Equal(err, nil)
at.Equal(w.Len(), 41)
}
func TestH264NalueSizeException(t *testing.T) {
at := assert.New(t)
nalu := []byte{
0x00, 0x00, 0x10,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w)
at.Equal(err, fmt.Errorf("video data not match"))
}
func TestH264Mp4Demux(t *testing.T) {
at := assert.New(t)
nalu := []byte{
0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x04, 0x68,
0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x02, 0x65, 0x23,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w)
at.Equal(err, nil)
at.Equal(w.Len(), 47)
at.Equal(w.Bytes(), []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,
0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23})
}
func TestH264Mp4DemuxException1(t *testing.T) {
at := assert.New(t)
nalu := []byte{
0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w)
at.Equal(err, naluBodyLenError)
}
func TestH264Mp4DemuxException2(t *testing.T) {
at := assert.New(t)
nalu := []byte{
0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00,
}
d := NewParser()
w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w)
at.Equal(err, naluBodyLenError)
}
================================================
FILE: parser/mp3/parser.go
================================================
package mp3
import (
"fmt"
)
type Parser struct {
samplingFrequency int
}
func NewParser() *Parser {
return &Parser{}
}
// sampling_frequency - indicates the sampling frequency, according to the following table.
// '00' 44.1 kHz
// '01' 48 kHz
// '10' 32 kHz
// '11' reserved
var mp3Rates = []int{44100, 48000, 32000}
var (
errMp3DataInvalid = fmt.Errorf("mp3data invalid")
errIndexInvalid = fmt.Errorf("invalid rate index")
)
func (parser *Parser) Parse(src []byte) error {
if len(src) < 3 {
return errMp3DataInvalid
}
index := (src[2] >> 2) & 0x3
if index <= byte(len(mp3Rates)-1) {
parser.samplingFrequency = mp3Rates[index]
return nil
}
return errIndexInvalid
}
func (parser *Parser) SampleRate() int {
if parser.samplingFrequency == 0 {
parser.samplingFrequency = 44100
}
return parser.samplingFrequency
}
================================================
FILE: parser/parser.go
================================================
package parser
import (
"fmt"
"io"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/parser/aac"
"github.com/gwuhaolin/livego/parser/h264"
"github.com/gwuhaolin/livego/parser/mp3"
)
var (
errNoAudio = fmt.Errorf("demuxer no audio")
)
type CodecParser struct {
aac *aac.Parser
mp3 *mp3.Parser
h264 *h264.Parser
}
func NewCodecParser() *CodecParser {
return &CodecParser{}
}
func (codeParser *CodecParser) SampleRate() (int, error) {
if codeParser.aac == nil && codeParser.mp3 == nil {
return 0, errNoAudio
}
if codeParser.aac != nil {
return codeParser.aac.SampleRate(), nil
}
return codeParser.mp3.SampleRate(), nil
}
func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err error) {
switch p.IsVideo {
case true:
f, ok := p.Header.(av.VideoPacketHeader)
if ok {
if f.CodecID() == av.VIDEO_H264 {
if codeParser.h264 == nil {
codeParser.h264 = h264.NewParser()
}
err = codeParser.h264.Parse(p.Data, f.IsSeq(), w)
}
}
case false:
f, ok := p.Header.(av.AudioPacketHeader)
if ok {
switch f.SoundFormat() {
case av.SOUND_AAC:
if codeParser.aac == nil {
codeParser.aac = aac.NewParser()
}
err = codeParser.aac.Parse(p.Data, f.AACPacketType(), w)
case av.SOUND_MP3:
if codeParser.mp3 == nil {
codeParser.mp3 = mp3.NewParser()
}
err = codeParser.mp3.Parse(p.Data)
}
}
}
return
}
================================================
FILE: protocol/amf/amf.go
================================================
package amf
import (
"fmt"
"io"
)
func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err error) {
var v interface{}
for {
v, err = d.Decode(r, ver)
if err != nil {
break
}
ret = append(ret, v)
}
return
}
func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {
switch ver {
case 0:
return d.DecodeAmf0(r)
case 3:
return d.DecodeAmf3(r)
}
return nil, fmt.Errorf("decode amf: unsupported version %d", ver)
}
func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) {
for _, v := range val {
if _, err := e.Encode(w, v, ver); err != nil {
return 0, err
}
}
return 0, nil
}
func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error) {
switch ver {
case AMF0:
return e.EncodeAmf0(w, val)
case AMF3:
return e.EncodeAmf3(w, val)
}
return 0, fmt.Errorf("encode amf: unsupported version %d", ver)
}
================================================
FILE: protocol/amf/amf_test.go
================================================
package amf
import (
"bytes"
"fmt"
"reflect"
"testing"
"time"
)
func EncodeAndDecode(val interface{}, ver Version) (result interface{}, err error) {
enc := new(Encoder)
dec := new(Decoder)
buf := new(bytes.Buffer)
_, err = enc.Encode(buf, val, ver)
if err != nil {
return nil, fmt.Errorf("error in encode: %s", err)
}
result, err = dec.Decode(buf, ver)
if err != nil {
return nil, fmt.Errorf("error in decode: %s", err)
}
return
}
func Compare(val interface{}, ver Version, name string, t *testing.T) {
result, err := EncodeAndDecode(val, ver)
if err != nil {
t.Errorf("%s: %s", name, err)
}
if !reflect.DeepEqual(val, result) {
val_v := reflect.ValueOf(val)
result_v := reflect.ValueOf(result)
t.Errorf("%s: comparison failed between %+v (%s) and %+v (%s)", name, val, val_v.Type(), result, result_v.Type())
Dump("expected", val)
Dump("got", result)
}
// if val != result {
// t.Errorf("%s: comparison failed between %+v and %+v", name, val, result)
// }
}
func TestAmf0Number(t *testing.T) {
Compare(float64(3.14159), 0, "amf0 number float", t)
Compare(float64(124567890), 0, "amf0 number high", t)
Compare(float64(-34.2), 0, "amf0 number negative", t)
}
func TestAmf0String(t *testing.T) {
Compare("a pup!", 0, "amf0 string simple", t)
Compare("日本語", 0, "amf0 string utf8", t)
}
func TestAmf0Boolean(t *testing.T) {
Compare(true, 0, "amf0 boolean true", t)
Compare(false, 0, "amf0 boolean false", t)
}
func TestAmf0Null(t *testing.T) {
Compare(nil, 0, "amf0 boolean nil", t)
}
func TestAmf0Object(t *testing.T) {
obj := make(Object)
obj["dog"] = "alfie"
obj["coffee"] = true
obj["drugs"] = false
obj["pi"] = 3.14159
res, err := EncodeAndDecode(obj, 0)
if err != nil {
t.Errorf("amf0 object: %s", err)
}
result, ok := res.(Object)
if ok != true {
t.Errorf("amf0 object conversion failed")
}
if result["dog"] != "alfie" {
t.Errorf("amf0 object string: comparison failed")
}
if result["coffee"] != true {
t.Errorf("amf0 object true: comparison failed")
}
if result["drugs"] != false {
t.Errorf("amf0 object false: comparison failed")
}
if result["pi"] != float64(3.14159) {
t.Errorf("amf0 object float: comparison failed")
}
}
func TestAmf0Array(t *testing.T) {
arr := [5]float64{1, 2, 3, 4, 5}
res, err := EncodeAndDecode(arr, 0)
if err != nil {
t.Errorf("amf0 object: %s", err)
}
result, ok := res.(Array)
if ok != true {
t.Errorf("amf0 array conversion failed")
}
for i := 0; i < len(arr); i++ {
if arr[i] != result[i] {
t.Errorf("amf0 array %d comparison failed: %v / %v", i, arr[i], result[i])
}
}
}
func TestAmf3Integer(t *testing.T) {
Compare(int32(0), 3, "amf3 integer zero", t)
Compare(int32(1245), 3, "amf3 integer low", t)
Compare(int32(123456), 3, "amf3 integer high", t)
}
func TestAmf3Double(t *testing.T) {
Compare(float64(3.14159), 3, "amf3 double float", t)
Compare(float64(1234567890), 3, "amf3 double high", t)
Compare(float64(-12345), 3, "amf3 double negative", t)
}
func TestAmf3String(t *testing.T) {
Compare("a pup!", 0, "amf0 string simple", t)
Compare("日本語", 0, "amf0 string utf8", t)
}
func TestAmf3Boolean(t *testing.T) {
Compare(true, 3, "amf3 boolean true", t)
Compare(false, 3, "amf3 boolean false", t)
}
func TestAmf3Null(t *testing.T) {
Compare(nil, 3, "amf3 boolean nil", t)
}
func TestAmf3Date(t *testing.T) {
t1 := time.Unix(time.Now().Unix(), 0).UTC() // nanoseconds discarded
t2 := time.Date(1983, 9, 4, 12, 4, 8, 0, time.UTC)
Compare(t1, 3, "amf3 date now", t)
Compare(t2, 3, "amf3 date earlier", t)
}
func TestAmf3Array(t *testing.T) {
obj := make(Object)
obj["key"] = "val"
var arr Array
arr = append(arr, "amf")
arr = append(arr, float64(2))
arr = append(arr, -34.95)
arr = append(arr, true)
arr = append(arr, false)
res, err := EncodeAndDecode(arr, 3)
if err != nil {
t.Errorf("amf3 object: %s", err)
}
result, ok := res.(Array)
if ok != true {
t.Errorf("amf3 array conversion failed: %+v", res)
}
for i := 0; i < len(arr); i++ {
if arr[i] != result[i] {
t.Errorf("amf3 array %d comparison failed: %v / %v", i, arr[i], result[i])
}
}
}
func TestAmf3ByteArray(t *testing.T) {
enc := new(Encoder)
dec := new(Decoder)
buf := new(bytes.Buffer)
expect := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x00}
enc.EncodeAmf3ByteArray(buf, expect, true)
result, err := dec.DecodeAmf3ByteArray(buf, true)
if err != nil {
t.Errorf("err: %s", err)
}
if bytes.Compare(result, expect) != 0 {
t.Errorf("expected: %+v, got %+v", expect, buf)
}
}
================================================
FILE: protocol/amf/const.go
================================================
package amf
import (
"io"
)
const (
AMF0 = 0x00
AMF3 = 0x03
)
const (
AMF0_NUMBER_MARKER = 0x00
AMF0_BOOLEAN_MARKER = 0x01
AMF0_STRING_MARKER = 0x02
AMF0_OBJECT_MARKER = 0x03
AMF0_MOVIECLIP_MARKER = 0x04
AMF0_NULL_MARKER = 0x05
AMF0_UNDEFINED_MARKER = 0x06
AMF0_REFERENCE_MARKER = 0x07
AMF0_ECMA_ARRAY_MARKER = 0x08
AMF0_OBJECT_END_MARKER = 0x09
AMF0_STRICT_ARRAY_MARKER = 0x0a
AMF0_DATE_MARKER = 0x0b
AMF0_LONG_STRING_MARKER = 0x0c
AMF0_UNSUPPORTED_MARKER = 0x0d
AMF0_RECORDSET_MARKER = 0x0e
AMF0_XML_DOCUMENT_MARKER = 0x0f
AMF0_TYPED_OBJECT_MARKER = 0x10
AMF0_ACMPLUS_OBJECT_MARKER = 0x11
)
const (
AMF0_BOOLEAN_FALSE = 0x00
AMF0_BOOLEAN_TRUE = 0x01
AMF0_STRING_MAX = 65535
AMF3_INTEGER_MAX = 536870911
)
const (
AMF3_UNDEFINED_MARKER = 0x00
AMF3_NULL_MARKER = 0x01
AMF3_FALSE_MARKER = 0x02
AMF3_TRUE_MARKER = 0x03
AMF3_INTEGER_MARKER = 0x04
AMF3_DOUBLE_MARKER = 0x05
AMF3_STRING_MARKER = 0x06
AMF3_XMLDOC_MARKER = 0x07
AMF3_DATE_MARKER = 0x08
AMF3_ARRAY_MARKER = 0x09
AMF3_OBJECT_MARKER = 0x0a
AMF3_XMLSTRING_MARKER = 0x0b
AMF3_BYTEARRAY_MARKER = 0x0c
)
type ExternalHandler func(*Decoder, io.Reader) (interface{}, error)
type Decoder struct {
refCache []interface{}
stringRefs []string
objectRefs []interface{}
traitRefs []Trait
externalHandlers map[string]ExternalHandler
}
func NewDecoder() *Decoder {
return &Decoder{
externalHandlers: make(map[string]ExternalHandler),
}
}
func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandler) {
d.externalHandlers[name] = f
}
type Encoder struct {
}
type Version uint8
type Array []interface{}
type Object map[string]interface{}
type TypedObject struct {
Type string
Object Object
}
type Trait struct {
Type string
Externalizable bool
Dynamic bool
Properties []string
}
func NewTrait() *Trait {
return &Trait{}
}
func NewTypedObject() *TypedObject {
return &TypedObject{
Type: "",
Object: make(Object),
}
}
================================================
FILE: protocol/amf/decoder_amf0.go
================================================
package amf
import (
"encoding/binary"
"fmt"
"io"
)
// amf0 polymorphic router
func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
marker, err := ReadMarker(r)
if err != nil {
return nil, err
}
switch marker {
case AMF0_NUMBER_MARKER:
return d.DecodeAmf0Number(r, false)
case AMF0_BOOLEAN_MARKER:
return d.DecodeAmf0Boolean(r, false)
case AMF0_STRING_MARKER:
return d.DecodeAmf0String(r, false)
case AMF0_OBJECT_MARKER:
return d.DecodeAmf0Object(r, false)
case AMF0_MOVIECLIP_MARKER:
return nil, fmt.Errorf("decode amf0: unsupported type movieclip")
case AMF0_NULL_MARKER:
return d.DecodeAmf0Null(r, false)
case AMF0_UNDEFINED_MARKER:
return d.DecodeAmf0Undefined(r, false)
case AMF0_REFERENCE_MARKER:
return nil, fmt.Errorf("decode amf0: unsupported type reference")
case AMF0_ECMA_ARRAY_MARKER:
return d.DecodeAmf0EcmaArray(r, false)
case AMF0_STRICT_ARRAY_MARKER:
return d.DecodeAmf0StrictArray(r, false)
case AMF0_DATE_MARKER:
return d.DecodeAmf0Date(r, false)
case AMF0_LONG_STRING_MARKER:
return d.DecodeAmf0LongString(r, false)
case AMF0_UNSUPPORTED_MARKER:
return d.DecodeAmf0Unsupported(r, false)
case AMF0_RECORDSET_MARKER:
return nil, fmt.Errorf("decode amf0: unsupported type recordset")
case AMF0_XML_DOCUMENT_MARKER:
return d.DecodeAmf0XmlDocument(r, false)
case AMF0_TYPED_OBJECT_MARKER:
return d.DecodeAmf0TypedObject(r, false)
case AMF0_ACMPLUS_OBJECT_MARKER:
return d.DecodeAmf3(r)
}
return nil, fmt.Errorf("decode amf0: unsupported type %d", marker)
}
// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &result)
if err != nil {
return float64(0), fmt.Errorf("amf0 decode: unable to read number: %s", err)
}
return
}
// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil {
return
}
var b byte
if b, err = ReadByte(r); err != nil {
return
}
if b == AMF0_BOOLEAN_FALSE {
return false, nil
} else if b == AMF0_BOOLEAN_TRUE {
return true, nil
}
return false, fmt.Errorf("decode amf0: unexpected value %v for boolean", b)
}
// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil {
return
}
var length uint16
err = binary.Read(r, binary.BigEndian, &length)
if err != nil {
return "", fmt.Errorf("decode amf0: unable to decode string length: %s", err)
}
var bytes = make([]byte, length)
if bytes, err = ReadBytes(r, int(length)); err != nil {
return "", fmt.Errorf("decode amf0: unable to decode string value: %s", err)
}
return string(bytes), nil
}
// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) {
if err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil {
return nil, err
}
result := make(Object)
d.refCache = append(d.refCache, result)
for {
key, err := d.DecodeAmf0String(r, false)
if err != nil {
return nil, err
}
if key == "" {
if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {
return nil, fmt.Errorf("decode amf0: expected object end marker: %s", err)
}
break
}
value, err := d.DecodeAmf0(r)
if err != nil {
return nil, fmt.Errorf("decode amf0: unable to decode object value: %s", err)
}
result[key] = value
}
return result, nil
}
// marker: 1 byte 0x05
// no additional data
func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
err = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER)
return
}
// marker: 1 byte 0x06
// no additional data
func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
err = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER)
return
}
// marker: 1 byte 0x07
// format: 2 byte big endian uint16
/*
func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) {
if err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil {
return nil, err
}
var err error
var ref uint16
err = binary.Read(r, binary.BigEndian, &ref)
if err != nil {
return nil, fmt.Errorf("decode amf0: unable to decode reference id: %s", err)
}
if int(ref) > len(d.refCache) {
return nil, fmt.Errorf("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache))
}
result := d.refCache[ref]
return result, nil
}
*/
// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (Object, error) {
if err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil {
return nil, err
}
var length uint32
err := binary.Read(r, binary.BigEndian, &length)
result, err := d.DecodeAmf0Object(r, false)
if err != nil {
return nil, fmt.Errorf("decode amf0: unable to decode ecma array object: %s", err)
}
return result, nil
}
// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result Array, err error) {
if err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil {
return nil, err
}
var length uint32
err = binary.Read(r, binary.BigEndian, &length)
if err != nil {
return nil, fmt.Errorf("decode amf0: unable to decode strict array length: %s", err)
}
d.refCache = append(d.refCache, result)
for i := uint32(0); i < length; i++ {
tmp, err := d.DecodeAmf0(r)
if err != nil {
return nil, fmt.Errorf("decode amf0: unable to decode strict array object: %s", err)
}
result = append(result, tmp)
}
return result, nil
}
// marker: 1 byte 0x0b
// format:
// - normal number format:
// - 8 byte big endian float64
// - 2 byte unused
func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil {
return
}
if result, err = d.DecodeAmf0Number(r, false); err != nil {
return float64(0), fmt.Errorf("decode amf0: unable to decode float in date: %s", err)
}
if _, err = ReadBytes(r, 2); err != nil {
return float64(0), fmt.Errorf("decode amf0: unable to read 2 trail bytes in date: %s", err)
}
return
}
// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil {
return
}
var length uint32
err = binary.Read(r, binary.BigEndian, &length)
if err != nil {
return "", fmt.Errorf("decode amf0: unable to decode long string length: %s", err)
}
var bytes = make([]byte, length)
if bytes, err = ReadBytes(r, int(length)); err != nil {
return "", fmt.Errorf("decode amf0: unable to decode long string value: %s", err)
}
return string(bytes), nil
}
// marker: 1 byte 0x0d
// no additional data
func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) {
err = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER)
return
}
// marker: 1 byte 0x0f
// format:
// - normal long string format
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) {
if err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil {
return
}
return d.DecodeAmf0LongString(r, false)
}
// marker: 1 byte 0x10
// format:
// - normal string format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
// - normal object format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (TypedObject, error) {
result := *new(TypedObject)
err := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER)
if err != nil {
return result, err
}
d.refCache = append(d.refCache, result)
result.Type, err = d.DecodeAmf0String(r, false)
if err != nil {
return result, fmt.Errorf("decode amf0: typed object unable to determine type: %s", err)
}
result.Object, err = d.DecodeAmf0Object(r, false)
if err != nil {
return result, fmt.Errorf("decode amf0: typed object unable to determine object: %s", err)
}
return result, nil
}
================================================
FILE: protocol/amf/decoder_amf0_test.go
================================================
package amf
import (
"bytes"
"testing"
)
func TestDecodeAmf0Number(t *testing.T) {
buf := bytes.NewReader([]byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})
expect := float64(1.2)
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test number interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Number(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test number interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0Number(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0BooleanTrue(t *testing.T) {
buf := bytes.NewReader([]byte{0x01, 0x01})
expect := true
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test boolean interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Boolean(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test boolean interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0Boolean(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0BooleanFalse(t *testing.T) {
buf := bytes.NewReader([]byte{0x01, 0x00})
expect := false
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test boolean interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Boolean(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test boolean interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0Boolean(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0String(t *testing.T) {
buf := bytes.NewReader([]byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f})
expect := "foo"
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test string interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0String(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test string interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0String(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0Object(t *testing.T) {
buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
obj, ok := got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
// Test object interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Object(buf, true)
if err != nil {
t.Errorf("%s", err)
}
obj, ok = got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
// Test object interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0Object(buf, false)
if err != nil {
t.Errorf("%s", err)
}
obj, ok = got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
}
func TestDecodeAmf0Null(t *testing.T) {
buf := bytes.NewReader([]byte{0x05})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
// Test null interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Null(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
}
func TestDecodeAmf0Undefined(t *testing.T) {
buf := bytes.NewReader([]byte{0x06})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
// Test undefined interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Undefined(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
}
/*
func TestDecodeReference(t *testing.T) {
buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x09})
dec := &Decoder{}
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
obj, ok := got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
_, ok2 := obj["foo"].(Object)
if ok2 != true {
t.Errorf("expected foo value to cast to object")
}
}
*/
func TestDecodeAmf0EcmaArray(t *testing.T) {
buf := bytes.NewReader([]byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
obj, ok := got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
// Test ecma array interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0EcmaArray(buf, true)
if err != nil {
t.Errorf("%s", err)
}
obj, ok = got.(Object)
if ok != true {
t.Errorf("expected result to cast to object")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
// Test ecma array interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0EcmaArray(buf, false)
if err != nil {
t.Errorf("%s", err)
}
obj, ok = got.(Object)
if ok != true {
t.Errorf("expected result to cast to ecma array")
}
if obj["foo"] != "bar" {
t.Errorf("expected {'foo'='bar'}, got %v", obj)
}
}
func TestDecodeAmf0StrictArray(t *testing.T) {
buf := bytes.NewReader([]byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
arr, ok := got.(Array)
if ok != true {
t.Errorf("expected result to cast to strict array")
}
if arr[0] != float64(5) {
t.Errorf("expected array[0] to be 5, got %v", arr[0])
}
if arr[1] != "foo" {
t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
}
if arr[2] != nil {
t.Errorf("expected array[2] to be nil, got %v", arr[2])
}
// Test strict array interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0StrictArray(buf, true)
if err != nil {
t.Errorf("%s", err)
}
arr, ok = got.(Array)
if ok != true {
t.Errorf("expected result to cast to strict array")
}
if arr[0] != float64(5) {
t.Errorf("expected array[0] to be 5, got %v", arr[0])
}
if arr[1] != "foo" {
t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
}
if arr[2] != nil {
t.Errorf("expected array[2] to be nil, got %v", arr[2])
}
// Test strict array interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0StrictArray(buf, false)
if err != nil {
t.Errorf("%s", err)
}
arr, ok = got.(Array)
if ok != true {
t.Errorf("expected result to cast to strict array")
}
if arr[0] != float64(5) {
t.Errorf("expected array[0] to be 5, got %v", arr[0])
}
if arr[1] != "foo" {
t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
}
if arr[2] != nil {
t.Errorf("expected array[2] to be nil, got %v", arr[2])
}
}
func TestDecodeAmf0Date(t *testing.T) {
buf := bytes.NewReader([]byte{0x0b, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
expect := float64(5)
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test date interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Date(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test date interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0Date(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0LongString(t *testing.T) {
buf := bytes.NewReader([]byte{0x0c, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})
expect := "foo"
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test long string interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0LongString(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test long string interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0LongString(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0Unsupported(t *testing.T) {
buf := bytes.NewReader([]byte{0x0d})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
// Test unsupported interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0Unsupported(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
}
func TestDecodeAmf0XmlDocument(t *testing.T) {
buf := bytes.NewReader([]byte{0x0f, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})
expect := "foo"
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test long string interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0XmlDocument(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
// Test long string interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0XmlDocument(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf0TypedObject(t *testing.T) {
buf := bytes.NewReader([]byte{
0x10, 0x00, 0x0F, 'o', 'r', 'g',
'.', 'a', 'm', 'f', '.', 'A',
'S', 'C', 'l', 'a', 's', 's',
0x00, 0x03, 'b', 'a', 'z', 0x05,
0x00, 0x03, 'f', 'o', 'o', 0x02,
0x00, 0x03, 'b', 'a', 'r', 0x00,
0x00, 0x09,
})
dec := &Decoder{}
// Test main interface
got, err := dec.DecodeAmf0(buf)
if err != nil {
t.Errorf("%s", err)
}
tobj, ok := got.(TypedObject)
if ok != true {
t.Errorf("expected result to cast to typed object, got %+v", tobj)
}
if tobj.Type != "org.amf.ASClass" {
t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
}
if tobj.Object["foo"] != "bar" {
t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
}
if tobj.Object["baz"] != nil {
t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
}
// Test typed object interface with marker
buf.Seek(0, 0)
got, err = dec.DecodeAmf0TypedObject(buf, true)
if err != nil {
t.Errorf("%s", err)
}
tobj, ok = got.(TypedObject)
if ok != true {
t.Errorf("expected result to cast to typed object, got %+v", tobj)
}
if tobj.Type != "org.amf.ASClass" {
t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
}
if tobj.Object["foo"] != "bar" {
t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
}
if tobj.Object["baz"] != nil {
t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
}
// Test typed object interface without marker
buf.Seek(1, 0)
got, err = dec.DecodeAmf0TypedObject(buf, false)
if err != nil {
t.Errorf("%s", err)
}
tobj, ok = got.(TypedObject)
if ok != true {
t.Errorf("expected result to cast to typed object, got %+v", tobj)
}
if tobj.Type != "org.amf.ASClass" {
t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
}
if tobj.Object["foo"] != "bar" {
t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
}
if tobj.Object["baz"] != nil {
t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
}
}
================================================
FILE: protocol/amf/decoder_amf3.go
================================================
package amf
import (
"encoding/binary"
"fmt"
"io"
"time"
)
// amf3 polymorphic router
func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) {
marker, err := ReadMarker(r)
if err != nil {
return nil, err
}
switch marker {
case AMF3_UNDEFINED_MARKER:
return d.DecodeAmf3Undefined(r, false)
case AMF3_NULL_MARKER:
return d.DecodeAmf3Null(r, false)
case AMF3_FALSE_MARKER:
return d.DecodeAmf3False(r, false)
case AMF3_TRUE_MARKER:
return d.DecodeAmf3True(r, false)
case AMF3_INTEGER_MARKER:
return d.DecodeAmf3Integer(r, false)
case AMF3_DOUBLE_MARKER:
return d.DecodeAmf3Double(r, false)
case AMF3_STRING_MARKER:
return d.DecodeAmf3String(r, false)
case AMF3_XMLDOC_MARKER:
return d.DecodeAmf3Xml(r, false)
case AMF3_DATE_MARKER:
return d.DecodeAmf3Date(r, false)
case AMF3_ARRAY_MARKER:
return d.DecodeAmf3Array(r, false)
case AMF3_OBJECT_MARKER:
return d.DecodeAmf3Object(r, false)
case AMF3_XMLSTRING_MARKER:
return d.DecodeAmf3Xml(r, false)
case AMF3_BYTEARRAY_MARKER:
return d.DecodeAmf3ByteArray(r, false)
}
return nil, fmt.Errorf("decode amf3: unsupported type %d", marker)
}
// marker: 1 byte 0x00
// no additional data
func (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
err = AssertMarker(r, decodeMarker, AMF3_UNDEFINED_MARKER)
return
}
// marker: 1 byte 0x01
// no additional data
func (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
err = AssertMarker(r, decodeMarker, AMF3_NULL_MARKER)
return
}
// marker: 1 byte 0x02
// no additional data
func (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (result bool, err error) {
err = AssertMarker(r, decodeMarker, AMF3_FALSE_MARKER)
result = false
return
}
// marker: 1 byte 0x03
// no additional data
func (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result bool, err error) {
err = AssertMarker(r, decodeMarker, AMF3_TRUE_MARKER)
result = true
return
}
// marker: 1 byte 0x04
func (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (result int32, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_INTEGER_MARKER); err != nil {
return
}
var u29 uint32
u29, err = d.decodeU29(r)
if err != nil {
return
}
result = int32(u29)
if result > 0xfffffff {
result = int32(u29 - 0x20000000)
}
return
}
// marker: 1 byte 0x05
func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (result float64, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_DOUBLE_MARKER); err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &result)
if err != nil {
return float64(0), fmt.Errorf("amf3 decode: unable to read double: %s", err)
}
return
}
// marker: 1 byte 0x06
// format:
// - u29 reference int. if reference, no more data. if not reference,
// length value of bytes to read to complete string.
func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result string, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_STRING_MARKER); err != nil {
return
}
var isRef bool
var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil {
return "", fmt.Errorf("amf3 decode: unable to decode string reference and length: %s", err)
}
if isRef {
result = d.stringRefs[refVal]
return
}
buf := make([]byte, refVal)
_, err = r.Read(buf)
if err != nil {
return "", fmt.Errorf("amf3 decode: unable to read string: %s", err)
}
result = string(buf)
if result != "" {
d.stringRefs = append(d.stringRefs, result)
}
return
}
// marker: 1 byte 0x08
// format:
// - u29 reference int, if reference, no more data
// - timestamp double
func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Time, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_DATE_MARKER); err != nil {
return
}
var isRef bool
var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode date reference and length: %s", err)
}
if isRef {
res, ok := d.objectRefs[refVal].(time.Time)
if ok != true {
return result, fmt.Errorf("amf3 decode: unable to extract time from date object references")
}
return res, err
}
var u64 float64
err = binary.Read(r, binary.BigEndian, &u64)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to read double: %s", err)
}
result = time.Unix(int64(u64/1000), 0).UTC()
d.objectRefs = append(d.objectRefs, result)
return
}
// marker: 1 byte 0x09
// format:
// - u29 reference int. if reference, no more data.
// - string representing associative array if present
// - n values (length of u29)
func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_ARRAY_MARKER); err != nil {
return
}
var isRef bool
var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode array reference and length: %s", err)
}
if isRef {
objRefId := refVal >> 1
res, ok := d.objectRefs[objRefId].(Array)
if ok != true {
return result, fmt.Errorf("amf3 decode: unable to extract array from object references")
}
return res, err
}
var key string
key, err = d.DecodeAmf3String(r, false)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to read key for array: %s", err)
}
if key != "" {
return result, fmt.Errorf("amf3 decode: array key is not empty, can't handle associative array")
}
for i := uint32(0); i < refVal; i++ {
tmp, err := d.DecodeAmf3(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: array element could not be decoded: %s", err)
}
result = append(result, tmp)
}
d.objectRefs = append(d.objectRefs, result)
return
}
// marker: 1 byte 0x09
// format: oh dear god
func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result interface{}, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_OBJECT_MARKER); err != nil {
return nil, err
}
// decode the initial u29
isRef, refVal, err := d.decodeReferenceInt(r)
if err != nil {
return nil, fmt.Errorf("amf3 decode: unable to decode object reference and length: %s", err)
}
// if this is a object reference only, grab it and return it
if isRef {
objRefId := refVal >> 1
return d.objectRefs[objRefId], nil
}
// each type has traits that are cached, if the peer sent a reference
// then we'll need to look it up and use it.
var trait Trait
traitIsRef := (refVal & 0x01) == 0
if traitIsRef {
traitRef := refVal >> 1
trait = d.traitRefs[traitRef]
} else {
// build a new trait from what's left of the given u29
trait = *NewTrait()
trait.Externalizable = (refVal & 0x02) != 0
trait.Dynamic = (refVal & 0x04) != 0
var cls string
cls, err = d.DecodeAmf3String(r, false)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to read trait type for object: %s", err)
}
trait.Type = cls
// traits have property keys, encoded as amf3 strings
propLength := refVal >> 3
for i := uint32(0); i < propLength; i++ {
tmp, err := d.DecodeAmf3String(r, false)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to read trait property for object: %s", err)
}
trait.Properties = append(trait.Properties, tmp)
}
d.traitRefs = append(d.traitRefs, trait)
}
d.objectRefs = append(d.objectRefs, result)
// objects can be externalizable, meaning that the system has no concrete understanding of
// their properties or how they are encoded. in that case, we need to find and delegate behavior
// to the right object.
if trait.Externalizable {
switch trait.Type {
case "DSA": // AsyncMessageExt
result, err = d.decodeAsyncMessageExt(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode dsa: %s", err)
}
case "DSK": // AcknowledgeMessageExt
result, err = d.decodeAcknowledgeMessageExt(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode dsk: %s", err)
}
case "flex.messaging.io.ArrayCollection":
result, err = d.decodeArrayCollection(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode ac: %s", err)
}
// store an extra reference to array collection container
d.objectRefs = append(d.objectRefs, result)
default:
fn, ok := d.externalHandlers[trait.Type]
if ok {
result, err = fn(d, r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to call external decoder for type %s: %s", trait.Type, err)
}
} else {
return result, fmt.Errorf("amf3 decode: unable to decode external type %s, no handler", trait.Type)
}
}
return result, err
}
var key string
var val interface{}
var obj Object
obj = make(Object)
// non-externalizable objects have property keys in traits, iterate through them
// and add the read values to the object
for _, key = range trait.Properties {
val, err = d.DecodeAmf3(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode object property: %s", err)
}
obj[key] = val
}
// if an object is dynamic, it can have extra key/value data at the end. in this case,
// read keys until we get an empty one.
if trait.Dynamic {
for {
key, err = d.DecodeAmf3String(r, false)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode dynamic key: %s", err)
}
if key == "" {
break
}
val, err = d.DecodeAmf3(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode dynamic value: %s", err)
}
obj[key] = val
}
}
result = obj
return
}
// marker: 1 byte 0x07 or 0x0b
// format:
// - u29 reference int. if reference, no more data. if not reference,
// length value of bytes to read to complete string.
func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string, err error) {
if decodeMarker {
var marker byte
marker, err = ReadMarker(r)
if err != nil {
return "", err
}
if (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) {
return "", fmt.Errorf("decode assert marker failed: expected %v or %v, got %v", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker)
}
}
var isRef bool
var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil {
return "", fmt.Errorf("amf3 decode: unable to decode xml reference and length: %s", err)
}
if isRef {
var ok bool
buf := d.objectRefs[refVal]
result, ok = buf.(string)
if ok != true {
return "", fmt.Errorf("amf3 decode: cannot coerce object reference into xml string")
}
return
}
buf := make([]byte, refVal)
_, err = r.Read(buf)
if err != nil {
return "", fmt.Errorf("amf3 decode: unable to read xml string: %s", err)
}
result = string(buf)
if result != "" {
d.objectRefs = append(d.objectRefs, result)
}
return
}
// marker: 1 byte 0x0c
// format:
// - u29 reference int. if reference, no more data. if not reference,
// length value of bytes to read.
func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []byte, err error) {
if err = AssertMarker(r, decodeMarker, AMF3_BYTEARRAY_MARKER); err != nil {
return
}
var isRef bool
var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to decode byte array reference and length: %s", err)
}
if isRef {
var ok bool
result, ok = d.objectRefs[refVal].([]byte)
if ok != true {
return result, fmt.Errorf("amf3 decode: unable to convert object ref to bytes")
}
return
}
result = make([]byte, refVal)
_, err = r.Read(result)
if err != nil {
return result, fmt.Errorf("amf3 decode: unable to read bytearray: %s", err)
}
d.objectRefs = append(d.objectRefs, result)
return
}
func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {
var b byte
for i := 0; i < 3; i++ {
b, err = ReadByte(r)
if err != nil {
return
}
result = (result << 7) + uint32(b&0x7F)
if (b & 0x80) == 0 {
return
}
}
b, err = ReadByte(r)
if err != nil {
return
}
result = ((result << 8) + uint32(b))
return
}
func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) {
u29, err := d.decodeU29(r)
if err != nil {
return false, 0, fmt.Errorf("amf3 decode: unable to decode reference int: %s", err)
}
isRef = u29&0x01 == 0
refVal = u29 >> 1
return
}
================================================
FILE: protocol/amf/decoder_amf3_external.go
================================================
package amf
import (
"fmt"
"io"
"math"
)
// Abstract external boilerplate
func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error) {
result = make(Object)
if err = d.decodeExternal(r, &result,
[]string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"},
[]string{"clientIdBytes", "messageIdBytes"}); err != nil {
return result, fmt.Errorf("unable to decode abstract external: %s", err)
}
return
}
// DSA
func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error) {
return d.decodeAsyncMessage(r)
}
func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) {
result, err = d.decodeAbstractMessage(r)
if err != nil {
return result, fmt.Errorf("unable to decode abstract for async: %s", err)
}
if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil {
return result, fmt.Errorf("unable to decode async external: %s", err)
}
return
}
// DSK
func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err error) {
return d.decodeAcknowledgeMessage(r)
}
func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) {
result, err = d.decodeAsyncMessage(r)
if err != nil {
return result, fmt.Errorf("unable to decode async for ack: %s", err)
}
if err = d.decodeExternal(r, &result); err != nil {
return result, fmt.Errorf("unable to decode ack external: %s", err)
}
return
}
// flex.messaging.io.ArrayCollection
func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {
result, err := d.DecodeAmf3(r)
if err != nil {
return result, fmt.Errorf("cannot decode child of array collection: %s", err)
}
return result, nil
}
func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string) (err error) {
var flagSet []uint8
var reservedPosition uint8
var fieldNames []string
flagSet, err = readFlags(r)
if err != nil {
return fmt.Errorf("unable to read flags: %s", err)
}
for i, flags := range flagSet {
if i < len(fieldSets) {
fieldNames = fieldSets[i]
} else {
fieldNames = []string{}
}
reservedPosition = uint8(len(fieldNames))
for p, field := range fieldNames {
flagBit := uint8(math.Exp2(float64(p)))
if (flags & flagBit) != 0 {
tmp, err := d.DecodeAmf3(r)
if err != nil {
return fmt.Errorf("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err)
}
(*obj)[field] = tmp
}
}
if (flags >> reservedPosition) != 0 {
for j := reservedPosition; j < 6; j++ {
if ((flags >> j) & 0x01) != 0 {
field := fmt.Sprintf("extra_%d_%d", i, j)
tmp, err := d.DecodeAmf3(r)
if err != nil {
return fmt.Errorf("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err)
}
(*obj)[field] = tmp
}
}
}
}
return
}
func readFlags(r io.Reader) (result []uint8, err error) {
for {
flag, err := ReadByte(r)
if err != nil {
return result, fmt.Errorf("unable to read flags: %s", err)
}
result = append(result, flag)
if (flag & 0x80) == 0 {
break
}
}
return
}
================================================
FILE: protocol/amf/decoder_amf3_test.go
================================================
package amf
import (
"bytes"
"testing"
)
type u29TestCase struct {
value uint32
expect []byte
}
var u29TestCases = []u29TestCase{
{1, []byte{0x01}},
{2, []byte{0x02}},
{127, []byte{0x7F}},
{128, []byte{0x81, 0x00}},
{255, []byte{0x81, 0x7F}},
{256, []byte{0x82, 0x00}},
{0x3FFF, []byte{0xFF, 0x7F}},
{0x4000, []byte{0x81, 0x80, 0x00}},
{0x7FFF, []byte{0x81, 0xFF, 0x7F}},
{0x8000, []byte{0x82, 0x80, 0x00}},
{0x1FFFFF, []byte{0xFF, 0xFF, 0x7F}},
{0x200000, []byte{0x80, 0xC0, 0x80, 0x00}},
{0x3FFFFF, []byte{0x80, 0xFF, 0xFF, 0xFF}},
{0x400000, []byte{0x81, 0x80, 0x80, 0x00}},
{0x0FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF}},
}
func TestDecodeAmf3Undefined(t *testing.T) {
buf := bytes.NewReader([]byte{0x00})
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
}
func TestDecodeAmf3Null(t *testing.T) {
buf := bytes.NewReader([]byte{0x01})
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if got != nil {
t.Errorf("expect nil got %v", got)
}
}
func TestDecodeAmf3False(t *testing.T) {
buf := bytes.NewReader([]byte{0x02})
expect := false
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf3True(t *testing.T) {
buf := bytes.NewReader([]byte{0x03})
expect := true
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeU29(t *testing.T) {
dec := new(Decoder)
for _, tc := range u29TestCases {
buf := bytes.NewBuffer(tc.expect)
n, err := dec.decodeU29(buf)
if err != nil {
t.Errorf("DecodeAmf3Integer error: %s", err)
}
if n != tc.value {
t.Errorf("DecodeAmf3Integer expect n %x got %x", tc.value, n)
}
}
}
func TestDecodeAmf3Integer(t *testing.T) {
dec := new(Decoder)
buf := bytes.NewReader([]byte{0x04, 0xFF, 0xFF, 0x7F})
expect := int32(2097151)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
buf.Seek(0, 0)
got, err = dec.DecodeAmf3Integer(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
buf.Seek(1, 0)
got, err = dec.DecodeAmf3Integer(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf3Double(t *testing.T) {
buf := bytes.NewReader([]byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})
expect := float64(1.2)
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf3String(t *testing.T) {
buf := bytes.NewReader([]byte{0x06, 0x07, 'f', 'o', 'o'})
expect := "foo"
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("%s", err)
}
if expect != got {
t.Errorf("expect %v got %v", expect, got)
}
}
func TestDecodeAmf3Array(t *testing.T) {
buf := bytes.NewReader([]byte{0x09, 0x13, 0x01,
0x06, 0x03, '1',
0x06, 0x03, '2',
0x06, 0x03, '3',
0x06, 0x03, '4',
0x06, 0x03, '5',
0x06, 0x03, '6',
0x06, 0x03, '7',
0x06, 0x03, '8',
0x06, 0x03, '9',
})
dec := new(Decoder)
expect := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}
got, err := dec.DecodeAmf3Array(buf, true)
if err != nil {
t.Errorf("err: %s", err)
}
for i, v := range expect {
if got[i] != v {
t.Errorf("expected array element %d to be %v, got %v", i, v, got[i])
}
}
}
func TestDecodeAmf3Object(t *testing.T) {
buf := bytes.NewReader([]byte{
0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',
'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',
's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',
'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',
})
dec := new(Decoder)
got, err := dec.DecodeAmf3(buf)
if err != nil {
t.Errorf("err: %s", err)
}
to, ok := got.(Object)
if ok != true {
t.Error("unable to cast object as typed object")
}
if to["foo"] != "bar" {
t.Errorf("expected foo to be bar, got: %+v", to["foo"])
}
if to["baz"] != nil {
t.Errorf("expected baz to be nil, got: %+v", to["baz"])
}
}
================================================
FILE: protocol/amf/encoder_amf0.go
================================================
package amf
import (
"encoding/binary"
"fmt"
"io"
"reflect"
)
// amf0 polymorphic router
func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) {
if val == nil {
return e.EncodeAmf0Null(w, true)
}
v := reflect.ValueOf(val)
if !v.IsValid() {
return e.EncodeAmf0Null(w, true)
}
switch v.Kind() {
case reflect.String:
str := v.String()
if len(str) <= AMF0_STRING_MAX {
return e.EncodeAmf0String(w, str, true)
} else {
return e.EncodeAmf0LongString(w, str, true)
}
case reflect.Bool:
return e.EncodeAmf0Boolean(w, v.Bool(), true)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return e.EncodeAmf0Number(w, float64(v.Int()), true)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return e.EncodeAmf0Number(w, float64(v.Uint()), true)
case reflect.Float32, reflect.Float64:
return e.EncodeAmf0Number(w, float64(v.Float()), true)
case reflect.Array, reflect.Slice:
length := v.Len()
arr := make(Array, length)
for i := 0; i < length; i++ {
arr[i] = v.Index(int(i)).Interface()
}
return e.EncodeAmf0StrictArray(w, arr, true)
case reflect.Map:
obj, ok := val.(Object)
if ok != true {
return 0, fmt.Errorf("encode amf0: unable to create object from map")
}
return e.EncodeAmf0Object(w, obj, true)
}
if _, ok := val.(TypedObject); ok {
return 0, fmt.Errorf("encode amf0: unsupported type typed object")
}
return 0, fmt.Errorf("encode amf0: unsupported type %s", v.Type())
}
// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_NUMBER_MARKER); err != nil {
return
}
n += 1
}
err = binary.Write(w, binary.BigEndian, &val)
if err != nil {
return
}
n += 8
return
}
// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_BOOLEAN_MARKER); err != nil {
return
}
n += 1
}
var m int
buf := make([]byte, 1)
if val {
buf[0] = AMF0_BOOLEAN_TRUE
} else {
buf[0] = AMF0_BOOLEAN_FALSE
}
m, err = w.Write(buf)
if err != nil {
return
}
n += m
return
}
// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_STRING_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint16(len(val))
err = binary.Write(w, binary.BigEndian, length)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode string length: %s", err)
}
n += 2
m, err = w.Write([]byte(val))
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode string value: %s", err)
}
n += m
return
}
// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_OBJECT_MARKER); err != nil {
return
}
n += 1
}
var m int
for k, v := range val {
m, err = e.EncodeAmf0String(w, k, false)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode object key: %s", err)
}
n += m
m, err = e.EncodeAmf0(w, v)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode object value: %s", err)
}
n += m
}
m, err = e.EncodeAmf0String(w, "", false)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode object empty string: %s", err)
}
n += m
err = WriteMarker(w, AMF0_OBJECT_END_MARKER)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to object end marker: %s", err)
}
n += 1
return
}
// marker: 1 byte 0x05
// no additional data
func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_NULL_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x06
// no additional data
func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_UNDEFINED_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_ECMA_ARRAY_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode ecma array length: %s", err)
}
n += 4
m, err = e.EncodeAmf0Object(w, val, false)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode ecma array object: %s", err)
}
n += m
return
}
// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_STRICT_ARRAY_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode strict array length: %s", err)
}
n += 4
for _, v := range val {
m, err = e.EncodeAmf0(w, v)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode strict array element: %s", err)
}
n += m
}
return
}
// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_LONG_STRING_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length)
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode long string length: %s", err)
}
n += 4
m, err = w.Write([]byte(val))
if err != nil {
return n, fmt.Errorf("encode amf0: unable to encode long string value: %s", err)
}
n += m
return
}
// marker: 1 byte 0x0d
// no additional data
func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF0_UNSUPPORTED_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x11
func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error {
return WriteMarker(w, AMF0_ACMPLUS_OBJECT_MARKER)
}
================================================
FILE: protocol/amf/encoder_amf0_test.go
================================================
package amf
import (
"bytes"
"encoding/binary"
"testing"
)
func TestEncodeAmf0Number(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}
enc := new(Encoder)
n, err := enc.EncodeAmf0(buf, float64(1.2))
if err != nil {
t.Errorf("%s", err)
}
if n != 9 {
t.Errorf("expected to write 9 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0BooleanTrue(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x01, 0x01}
enc := new(Encoder)
n, err := enc.EncodeAmf0(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if n != 2 {
t.Errorf("expected to write 2 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0BooleanFalse(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x01, 0x00}
enc := new(Encoder)
n, err := enc.EncodeAmf0(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if n != 2 {
t.Errorf("expected to write 2 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0String(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f}
enc := new(Encoder)
n, err := enc.EncodeAmf0(buf, "foo")
if err != nil {
t.Errorf("%s", err)
}
if n != 6 {
t.Errorf("expected to write 6 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0Object(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}
enc := new(Encoder)
obj := make(Object)
obj["foo"] = "bar"
n, err := enc.EncodeAmf0(buf, obj)
if err != nil {
t.Errorf("%s", err)
}
if n != 15 {
t.Errorf("expected to write 15 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0EcmaArray(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}
enc := new(Encoder)
obj := make(Object)
obj["foo"] = "bar"
_, err := enc.EncodeAmf0EcmaArray(buf, obj, true)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0StrictArray(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05}
enc := new(Encoder)
arr := make(Array, 3)
arr[0] = float64(5)
arr[1] = "foo"
arr[2] = nil
_, err := enc.EncodeAmf0StrictArray(buf, arr, true)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0Null(t *testing.T) {
buf := new(bytes.Buffer)
expect := []byte{0x05}
enc := new(Encoder)
n, err := enc.EncodeAmf0(buf, nil)
if err != nil {
t.Errorf("%s", err)
}
if n != 1 {
t.Errorf("expected to write 1 byte, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf0LongString(t *testing.T) {
buf := new(bytes.Buffer)
testBytes := []byte("12345678")
tbuf := new(bytes.Buffer)
for i := 0; i < 65536; i++ {
tbuf.Write(testBytes)
}
enc := new(Encoder)
_, err := enc.EncodeAmf0(buf, string(tbuf.Bytes()))
if err != nil {
t.Errorf("%s", err)
}
mbuf := make([]byte, 1)
_, err = buf.Read(mbuf)
if err != nil {
t.Errorf("error reading header")
}
if mbuf[0] != 0x0c {
t.Errorf("marker mismatch")
}
var length uint32
err = binary.Read(buf, binary.BigEndian, &length)
if err != nil {
t.Errorf("error reading buffer")
}
if length != (65536 * 8) {
t.Errorf("expected length to be %d, got %d", (65536 * 8), length)
}
tmpBuf := make([]byte, 8)
counter := 0
for buf.Len() > 0 {
n, err := buf.Read(tmpBuf)
if err != nil {
t.Fatalf("test long string result check, read data(%d) error: %s, n: %d", counter, err, n)
}
if n != 8 {
t.Fatalf("test long string result check, read data(%d) n: %d", counter, n)
}
if !bytes.Equal(testBytes, tmpBuf) {
t.Fatalf("test long string result check, read data % x", tmpBuf)
}
counter++
}
}
================================================
FILE: protocol/amf/encoder_amf3.go
================================================
package amf
import (
"encoding/binary"
"fmt"
"io"
"reflect"
"sort"
"time"
)
// amf3 polymorphic router
func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
if val == nil {
return e.EncodeAmf3Null(w, true)
}
v := reflect.ValueOf(val)
if !v.IsValid() {
return e.EncodeAmf3Null(w, true)
}
switch v.Kind() {
case reflect.String:
return e.EncodeAmf3String(w, v.String(), true)
case reflect.Bool:
if v.Bool() {
return e.EncodeAmf3True(w, true)
} else {
return e.EncodeAmf3False(w, true)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
n := v.Int()
if n >= 0 && n <= AMF3_INTEGER_MAX {
return e.EncodeAmf3Integer(w, uint32(n), true)
} else {
return e.EncodeAmf3Double(w, float64(n), true)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
n := v.Uint()
if n <= AMF3_INTEGER_MAX {
return e.EncodeAmf3Integer(w, uint32(n), true)
} else {
return e.EncodeAmf3Double(w, float64(n), true)
}
case reflect.Int64:
return e.EncodeAmf3Double(w, float64(v.Int()), true)
case reflect.Uint64:
return e.EncodeAmf3Double(w, float64(v.Uint()), true)
case reflect.Float32, reflect.Float64:
return e.EncodeAmf3Double(w, float64(v.Float()), true)
case reflect.Array, reflect.Slice:
length := v.Len()
arr := make(Array, length)
for i := 0; i < length; i++ {
arr[i] = v.Index(int(i)).Interface()
}
return e.EncodeAmf3Array(w, arr, true)
case reflect.Map:
obj, ok := val.(Object)
if ok != true {
return 0, fmt.Errorf("encode amf3: unable to create object from map")
}
to := *new(TypedObject)
to.Object = obj
return e.EncodeAmf3Object(w, to, true)
}
if tm, ok := val.(time.Time); ok {
return e.EncodeAmf3Date(w, tm, true)
}
if to, ok := val.(TypedObject); ok {
return e.EncodeAmf3Object(w, to, true)
}
return 0, fmt.Errorf("encode amf3: unsupported type %s", v.Type())
}
// marker: 1 byte 0x00
// no additional data
func (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_UNDEFINED_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x01
// no additional data
func (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_NULL_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x02
// no additional data
func (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_FALSE_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x03
// no additional data
func (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_TRUE_MARKER); err != nil {
return
}
n += 1
}
return
}
// marker: 1 byte 0x04
func (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_INTEGER_MARKER); err != nil {
return
}
n += 1
}
var m int
m, err = e.encodeAmf3Uint29(w, val)
if err != nil {
return
}
n += m
return
}
// marker: 1 byte 0x05
func (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_DOUBLE_MARKER); err != nil {
return
}
n += 1
}
err = binary.Write(w, binary.BigEndian, &val)
if err != nil {
return
}
n += 8
return
}
// marker: 1 byte 0x06
// format:
// - u29 reference int. if reference, no more data. if not reference,
// length value of bytes to read to complete string.
func (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_STRING_MARKER); err != nil {
return
}
n += 1
}
var m int
m, err = e.encodeAmf3Utf8(w, val)
if err != nil {
return
}
n += m
return
}
// marker: 1 byte 0x08
// format:
// - u29 reference int, if reference, no more data
// - timestamp double
func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_DATE_MARKER); err != nil {
return
}
n += 1
}
if err = WriteMarker(w, 0x01); err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
}
n += 1
u64 := float64(val.Unix()) * 1000.0
err = binary.Write(w, binary.BigEndian, &u64)
if err != nil {
return n, fmt.Errorf("amf3 encode: unable to write date double: %s", err)
}
n += 8
return
}
// marker: 1 byte 0x09
// format:
// - u29 reference int. if reference, no more data.
// - string representing associative array if present
// - n values (length of u29)
func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_ARRAY_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint32(len(val))
u29 := uint32(length<<1) | 0x01
m, err = e.encodeAmf3Uint29(w, u29)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
}
n += m
m, err = e.encodeAmf3Utf8(w, "")
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode empty string for array: %s", err)
}
n += m
for _, v := range val {
m, err := e.EncodeAmf3(w, v)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode array element: %s", err)
}
n += m
}
return
}
// marker: 1 byte 0x0a
// format: ugh
func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_OBJECT_MARKER); err != nil {
return
}
n += 1
}
m := 0
trait := *NewTrait()
trait.Type = val.Type
trait.Dynamic = false
trait.Externalizable = false
for k, _ := range val.Object {
trait.Properties = append(trait.Properties, k)
}
sort.Strings(trait.Properties)
var u29 uint32 = 0x03
if trait.Dynamic {
u29 |= 0x02 << 2
}
if trait.Externalizable {
u29 |= 0x01 << 2
}
u29 |= uint32(len(trait.Properties)) << 4
m, err = e.encodeAmf3Uint29(w, u29)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode trait header for object: %s", err)
}
n += m
m, err = e.encodeAmf3Utf8(w, trait.Type)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode trait type for object: %s", err)
}
n += m
for _, prop := range trait.Properties {
m, err = e.encodeAmf3Utf8(w, prop)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode trait property for object: %s", err)
}
n += m
}
if trait.Externalizable {
return n, fmt.Errorf("amf3 encode: cannot encode externalizable object")
}
for _, prop := range trait.Properties {
m, err = e.EncodeAmf3(w, val.Object[prop])
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode sealed object value: %s", err)
}
n += m
}
if trait.Dynamic {
for k, v := range val.Object {
var foundProp bool = false
for _, prop := range trait.Properties {
if prop == k {
foundProp = true
break
}
}
if foundProp != true {
m, err = e.encodeAmf3Utf8(w, k)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode dynamic object property key: %s", err)
}
n += m
m, err = e.EncodeAmf3(w, v)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode dynamic object value: %s", err)
}
n += m
}
m, err = e.encodeAmf3Utf8(w, "")
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode dynamic object ending marker string: %s", err)
}
n += m
}
}
return
}
// marker: 1 byte 0x0c
// format:
// - u29 reference int. if reference, no more data. if not reference,
// length value of bytes to read .
func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMarker bool) (n int, err error) {
if encodeMarker {
if err = WriteMarker(w, AMF3_BYTEARRAY_MARKER); err != nil {
return
}
n += 1
}
var m int
length := uint32(len(val))
u29 := (length << 1) | 1
m, err = e.encodeAmf3Uint29(w, u29)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode u29 for bytearray: %s", err)
}
n += m
m, err = w.Write(val)
if err != nil {
return n, fmt.Errorf("encode amf3: unable to encode bytearray value: %s", err)
}
n += m
return
}
func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err error) {
length := uint32(len(val))
u29 := uint32(length<<1) | 0x01
var m int
m, err = e.encodeAmf3Uint29(w, u29)
if err != nil {
return n, fmt.Errorf("amf3 encode: cannot encode u29 for string: %s", err)
}
n += m
m, err = w.Write([]byte(val))
if err != nil {
return n, fmt.Errorf("encode amf3: unable to encode string value: %s", err)
}
n += m
return
}
func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err error) {
if val <= 0x0000007F {
err = WriteByte(w, byte(val))
if err == nil {
n += 1
}
} else if val <= 0x00003FFF {
n, err = w.Write([]byte{byte(val>>7 | 0x80), byte(val & 0x7F)})
} else if val <= 0x001FFFFF {
n, err = w.Write([]byte{byte(val>>14 | 0x80), byte(val>>7&0x7F | 0x80), byte(val & 0x7F)})
} else if val <= 0x1FFFFFFF {
n, err = w.Write([]byte{byte(val>>22 | 0x80), byte(val>>15&0x7F | 0x80), byte(val>>8&0x7F | 0x80), byte(val)})
} else {
return n, fmt.Errorf("amf3 encode: cannot encode u29 with value %d (out of range)", val)
}
return
}
================================================
FILE: protocol/amf/encoder_amf3_test.go
================================================
package amf
import (
"bytes"
"testing"
)
func TestEncodeAmf3EmptyString(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x01}
_, err := enc.EncodeAmf3String(buf, "", false)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Undefined(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x00}
_, err := enc.EncodeAmf3Undefined(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Null(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x01}
_, err := enc.EncodeAmf3(buf, nil)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3False(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x02}
_, err := enc.EncodeAmf3(buf, false)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3True(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x03}
_, err := enc.EncodeAmf3(buf, true)
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Integer(t *testing.T) {
enc := new(Encoder)
for _, tc := range u29TestCases {
buf := new(bytes.Buffer)
_, err := enc.EncodeAmf3Integer(buf, tc.value, false)
if err != nil {
t.Errorf("EncodeAmf3Integer error: %s", err)
}
got := buf.Bytes()
if !bytes.Equal(tc.expect, got) {
t.Errorf("EncodeAmf3Integer expect n %x got %x", tc.value, got)
}
}
buf := new(bytes.Buffer)
expect := []byte{0x04, 0x80, 0xFF, 0xFF, 0xFF}
n, err := enc.EncodeAmf3(buf, uint32(4194303))
if err != nil {
t.Errorf("%s", err)
}
if n != 5 {
t.Errorf("expected to write 5 bytes, actual %d", n)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Double(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}
_, err := enc.EncodeAmf3(buf, float64(1.2))
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3String(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x06, 0x07, 'f', 'o', 'o'}
_, err := enc.EncodeAmf3(buf, "foo")
if err != nil {
t.Errorf("%s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Array(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{0x09, 0x13, 0x01,
0x06, 0x03, '1',
0x06, 0x03, '2',
0x06, 0x03, '3',
0x06, 0x03, '4',
0x06, 0x03, '5',
0x06, 0x03, '6',
0x06, 0x03, '7',
0x06, 0x03, '8',
0x06, 0x03, '9',
}
arr := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}
_, err := enc.EncodeAmf3(buf, arr)
if err != nil {
t.Errorf("err: %s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
}
}
func TestEncodeAmf3Object(t *testing.T) {
enc := new(Encoder)
buf := new(bytes.Buffer)
expect := []byte{
0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',
'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',
's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',
'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',
}
to := *NewTypedObject()
to.Type = "org.amf.ASClass"
to.Object["foo"] = "bar"
to.Object["baz"] = nil
_, err := enc.EncodeAmf3(buf, to)
if err != nil {
t.Errorf("err: %s", err)
}
if bytes.Compare(buf.Bytes(), expect) != 0 {
t.Errorf("expected buffer:\n%#v\ngot:\n%#v", expect, buf.Bytes())
}
}
================================================
FILE: protocol/amf/metadata.go
================================================
package amf
import (
"bytes"
"fmt"
log "github.com/sirupsen/logrus"
)
const (
ADD = 0x0
DEL = 0x3
)
const (
SetDataFrame string = "@setDataFrame"
OnMetaData string = "onMetaData"
)
var setFrameFrame []byte
func init() {
b := bytes.NewBuffer(nil)
encoder := &Encoder{}
if _, err := encoder.Encode(b, SetDataFrame, AMF0); err != nil {
log.Fatal(err)
}
setFrameFrame = b.Bytes()
}
func MetaDataReform(p []byte, flag uint8) ([]byte, error) {
r := bytes.NewReader(p)
decoder := &Decoder{}
switch flag {
case ADD:
v, err := decoder.Decode(r, AMF0)
if err != nil {
return nil, err
}
switch v.(type) {
case string:
vv := v.(string)
if vv != SetDataFrame {
tmplen := len(setFrameFrame)
b := make([]byte, tmplen+len(p))
copy(b, setFrameFrame)
copy(b[tmplen:], p)
p = b
}
default:
return nil, fmt.Errorf("setFrameFrame error")
}
case DEL:
v, err := decoder.Decode(r, AMF0)
if err != nil {
return nil, err
}
switch v.(type) {
case string:
vv := v.(string)
if vv == SetDataFrame {
p = p[len(setFrameFrame):]
}
default:
return nil, fmt.Errorf("metadata error")
}
default:
return nil, fmt.Errorf("invalid flag:%d", flag)
}
return p, nil
}
================================================
FILE: protocol/amf/util.go
================================================
package amf
import (
"encoding/json"
"fmt"
"io"
)
func DumpBytes(label string, buf []byte, size int) {
fmt.Printf("Dumping %s (%d bytes):\n", label, size)
for i := 0; i < size; i++ {
fmt.Printf("0x%02x ", buf[i])
}
fmt.Printf("\n")
}
func Dump(label string, val interface{}) error {
json, err := json.MarshalIndent(val, "", " ")
if err != nil {
return fmt.Errorf("Error dumping %s: %s", label, err)
}
fmt.Printf("Dumping %s:\n%s\n", label, json)
return nil
}
func WriteByte(w io.Writer, b byte) (err error) {
bytes := make([]byte, 1)
bytes[0] = b
_, err = WriteBytes(w, bytes)
return
}
func WriteBytes(w io.Writer, bytes []byte) (int, error) {
return w.Write(bytes)
}
func ReadByte(r io.Reader) (byte, error) {
bytes, err := ReadBytes(r, 1)
if err != nil {
return 0x00, err
}
return bytes[0], nil
}
func ReadBytes(r io.Reader, n int) ([]byte, error) {
bytes := make([]byte, n)
m, err := r.Read(bytes)
if err != nil {
return bytes, err
}
if m != n {
return bytes, fmt.Errorf("decode read bytes failed: expected %d got %d", m, n)
}
return bytes, nil
}
func WriteMarker(w io.Writer, m byte) error {
return WriteByte(w, m)
}
func ReadMarker(r io.Reader) (byte, error) {
return ReadByte(r)
}
func AssertMarker(r io.Reader, checkMarker bool, m byte) error {
if checkMarker == false {
return nil
}
marker, err := ReadMarker(r)
if err != nil {
return err
}
if marker != m {
return fmt.Errorf("decode assert marker failed: expected %v got %v", m, marker)
}
return nil
}
================================================
FILE: protocol/api/api.go
================================================
package api
import (
"encoding/json"
"fmt"
"net"
"net/http"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/rtmp"
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay"
jwtmiddleware "github.com/auth0/go-jwt-middleware"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
)
type Response struct {
w http.ResponseWriter
Status int `json:"status"`
Data interface{} `json:"data"`
}
func (r *Response) SendJson() (int, error) {
resp, _ := json.Marshal(r)
r.w.Header().Set("Content-Type", "application/json")
r.w.WriteHeader(r.Status)
return r.w.Write(resp)
}
type Operation struct {
Method string `json:"method"`
URL string `json:"url"`
Stop bool `json:"stop"`
}
type OperationChange struct {
Method string `json:"method"`
SourceURL string `json:"source_url"`
TargetURL string `json:"target_url"`
Stop bool `json:"stop"`
}
type ClientInfo struct {
url string
rtmpRemoteClient *rtmp.Client
rtmpLocalClient *rtmp.Client
}
type Server struct {
handler av.Handler
session map[string]*rtmprelay.RtmpRelay
rtmpAddr string
}
func NewServer(h av.Handler, rtmpAddr string) *Server {
return &Server{
handler: h,
session: make(map[string]*rtmprelay.RtmpRelay),
rtmpAddr: rtmpAddr,
}
}
func JWTMiddleware(next http.Handler) http.Handler {
isJWT := len(configure.Config.GetString("jwt.secret")) > 0
if !isJWT {
return next
}
log.Info("Using JWT middleware")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var algorithm jwt.SigningMethod
if len(configure.Config.GetString("jwt.algorithm")) > 0 {
algorithm = jwt.GetSigningMethod(configure.Config.GetString("jwt.algorithm"))
}
if algorithm == nil {
algorithm = jwt.SigningMethodHS256
}
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
Extractor: jwtmiddleware.FromFirst(jwtmiddleware.FromAuthHeader, jwtmiddleware.FromParameter("jwt")),
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte(configure.Config.GetString("jwt.secret")), nil
},
SigningMethod: algorithm,
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) {
res := &Response{
w: w,
Status: 403,
Data: err,
}
res.SendJson()
},
})
jwtMiddleware.HandlerWithNext(w, r, next.ServeHTTP)
})
}
func (s *Server) Serve(l net.Listener) error {
mux := http.NewServeMux()
mux.Handle("/statics/", http.StripPrefix("/statics/", http.FileServer(http.Dir("statics"))))
mux.HandleFunc("/control/push", func(w http.ResponseWriter, r *http.Request) {
s.handlePush(w, r)
})
mux.HandleFunc("/control/pull", func(w http.ResponseWriter, r *http.Request) {
s.handlePull(w, r)
})
mux.HandleFunc("/control/get", func(w http.ResponseWriter, r *http.Request) {
s.handleGet(w, r)
})
mux.HandleFunc("/control/reset", func(w http.ResponseWriter, r *http.Request) {
s.handleReset(w, r)
})
mux.HandleFunc("/control/delete", func(w http.ResponseWriter, r *http.Request) {
s.handleDelete(w, r)
})
mux.HandleFunc("/stat/livestat", func(w http.ResponseWriter, r *http.Request) {
s.GetLiveStatics(w, r)
})
http.Serve(l, JWTMiddleware(mux))
return nil
}
type stream struct {
Key string `json:"key"`
Url string `json:"url"`
StreamId uint32 `json:"stream_id"`
VideoTotalBytes uint64 `json:"video_total_bytes"`
VideoSpeed uint64 `json:"video_speed"`
AudioTotalBytes uint64 `json:"audio_total_bytes"`
AudioSpeed uint64 `json:"audio_speed"`
}
type streams struct {
Publishers []stream `json:"publishers"`
Players []stream `json:"players"`
}
//http://127.0.0.1:8090/stat/livestat
func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http.Request) {
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
room := ""
if err := req.ParseForm(); err == nil {
room = req.Form.Get("room")
}
rtmpStream := server.handler.(*rtmp.RtmpStream)
if rtmpStream == nil {
res.Status = 500
res.Data = "Get rtmp stream information error"
return
}
msgs := new(streams)
if room == "" {
rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
if s, ok := val.(*rtmp.Stream); ok {
if s.GetReader() != nil {
switch s.GetReader().(type) {
case *rtmp.VirReader:
v := s.GetReader().(*rtmp.VirReader)
msg := stream{key.(string), v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS,
v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS}
msgs.Publishers = append(msgs.Publishers, msg)
}
}
}
return true
})
rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
ws := val.(*rtmp.Stream).GetWs()
ws.Range(func(k, v interface{}) bool {
if pw, ok := v.(*rtmp.PackWriterCloser); ok {
if pw.GetWriter() != nil {
switch pw.GetWriter().(type) {
case *rtmp.VirWriter:
v := pw.GetWriter().(*rtmp.VirWriter)
msg := stream{key.(string), v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS,
v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS}
msgs.Players = append(msgs.Players, msg)
}
}
}
return true
})
return true
})
} else {
// Warning: The room should be in the "live/stream" format!
roomInfo, exists := (rtmpStream.GetStreams()).Load(room)
if exists == false {
res.Status = 404
res.Data = "room not found or inactive"
return
}
if s, ok := roomInfo.(*rtmp.Stream); ok {
if s.GetReader() != nil {
switch s.GetReader().(type) {
case *rtmp.VirReader:
v := s.GetReader().(*rtmp.VirReader)
msg := stream{room, v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS,
v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS}
msgs.Publishers = append(msgs.Publishers, msg)
}
}
s.GetWs().Range(func(k, v interface{}) bool {
if pw, ok := v.(*rtmp.PackWriterCloser); ok {
if pw.GetWriter() != nil {
switch pw.GetWriter().(type) {
case *rtmp.VirWriter:
v := pw.GetWriter().(*rtmp.VirWriter)
msg := stream{room, v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS,
v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS}
msgs.Players = append(msgs.Players, msg)
}
}
}
return true
})
}
}
//resp, _ := json.Marshal(msgs)
res.Data = msgs
}
//http://127.0.0.1:8090/control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) {
var retString string
var err error
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
if req.ParseForm() != nil {
res.Status = 400
res.Data = "url: /control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456"
return
}
oper := req.Form.Get("oper")
app := req.Form.Get("app")
name := req.Form.Get("name")
url := req.Form.Get("url")
log.Debugf("control pull: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url)
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) {
res.Status = 400
res.Data = "control push parameter error, please check them."
return
}
remoteurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name
localurl := url
keyString := "pull:" + app + "/" + name
if oper == "stop" {
pullRtmprelay, found := s.session[keyString]
if !found {
retString = fmt.Sprintf("session key[%s] not exist, please check it again.", keyString)
res.Status = 400
res.Data = retString
return
}
log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl)
pullRtmprelay.Stop()
delete(s.session, keyString)
retString = fmt.Sprintf("push url stop %s ok
", url)
res.Status = 400
res.Data = retString
log.Debugf("pull stop return %s", retString)
} else {
pullRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl)
log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl)
err = pullRtmprelay.Start()
if err != nil {
res.Status = 400
retString = fmt.Sprintf("push error=%v", err)
} else {
s.session[keyString] = pullRtmprelay
retString = fmt.Sprintf("pull url start %s ok
", url)
}
res.Data = retString
log.Debugf("pull start return %s", retString)
}
}
//http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) {
var retString string
var err error
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
if req.ParseForm() != nil {
res.Data = "url: /control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456"
return
}
oper := req.Form.Get("oper")
app := req.Form.Get("app")
name := req.Form.Get("name")
url := req.Form.Get("url")
log.Debugf("control push: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url)
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) {
res.Data = "control push parameter error, please check them."
return
}
localurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name
remoteurl := url
keyString := "push:" + app + "/" + name
if oper == "stop" {
pushRtmprelay, found := s.session[keyString]
if !found {
retString = fmt.Sprintf("session key[%s] not exist, please check it again.
", keyString)
res.Data = retString
return
}
log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl)
pushRtmprelay.Stop()
delete(s.session, keyString)
retString = fmt.Sprintf("push url stop %s ok
", url)
res.Data = retString
log.Debugf("push stop return %s", retString)
} else {
pushRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl)
log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl)
err = pushRtmprelay.Start()
if err != nil {
retString = fmt.Sprintf("push error=%v", err)
} else {
retString = fmt.Sprintf("push url start %s ok
", url)
s.session[keyString] = pushRtmprelay
}
res.Data = retString
log.Debugf("push start return %s", retString)
}
}
//http://127.0.0.1:8090/control/reset?room=ROOM_NAME
func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
if err := r.ParseForm(); err != nil {
res.Status = 400
res.Data = "url: /control/reset?room="
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/reset?room="
return
}
msg, err := configure.RoomKeys.SetKey(room)
if err != nil {
msg = err.Error()
res.Status = 400
}
res.Data = msg
}
//http://127.0.0.1:8090/control/get?room=ROOM_NAME
func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
if err := r.ParseForm(); err != nil {
res.Status = 400
res.Data = "url: /control/get?room="
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/get?room="
return
}
msg, err := configure.RoomKeys.GetKey(room)
if err != nil {
msg = err.Error()
res.Status = 400
}
res.Data = msg
}
//http://127.0.0.1:8090/control/delete?room=ROOM_NAME
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {
res := &Response{
w: w,
Data: nil,
Status: 200,
}
defer res.SendJson()
if err := r.ParseForm(); err != nil {
res.Status = 400
res.Data = "url: /control/delete?room="
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/delete?room="
return
}
if configure.RoomKeys.DeleteChannel(room) {
res.Data = "Ok"
return
}
res.Status = 404
res.Data = "room not found"
}
================================================
FILE: protocol/hls/align.go
================================================
package hls
const (
syncms = 2 // ms
)
type align struct {
frameNum uint64
frameBase uint64
}
func (a *align) align(dts *uint64, inc uint32) {
aFrameDts := *dts
estPts := a.frameBase + a.frameNum*uint64(inc)
var dPts uint64
if estPts >= aFrameDts {
dPts = estPts - aFrameDts
} else {
dPts = aFrameDts - estPts
}
if dPts <= uint64(syncms)*h264_default_hz {
a.frameNum++
*dts = estPts
return
}
a.frameNum = 1
a.frameBase = aFrameDts
}
================================================
FILE: protocol/hls/audio_cache.go
================================================
package hls
import "bytes"
const (
cache_max_frames byte = 6
audio_cache_len int = 10 * 1024
)
type audioCache struct {
soundFormat byte
num byte
offset int
pts uint64
buf *bytes.Buffer
}
func newAudioCache() *audioCache {
return &audioCache{
buf: bytes.NewBuffer(make([]byte, audio_cache_len)),
}
}
func (a *audioCache) Cache(src []byte, pts uint64) bool {
if a.num == 0 {
a.offset = 0
a.pts = pts
a.buf.Reset()
}
a.buf.Write(src)
a.offset += len(src)
a.num++
return false
}
func (a *audioCache) GetFrame() (int, uint64, []byte) {
a.num = 0
return a.offset, a.pts, a.buf.Bytes()
}
func (a *audioCache) CacheNum() byte {
return a.num
}
================================================
FILE: protocol/hls/cache.go
================================================
package hls
import (
"bytes"
"container/list"
"fmt"
"sync"
)
const (
maxTSCacheNum = 3
)
var (
ErrNoKey = fmt.Errorf("No key for cache")
)
type TSCacheItem struct {
id string
num int
lock sync.RWMutex
ll *list.List
lm map[string]TSItem
}
func NewTSCacheItem(id string) *TSCacheItem {
return &TSCacheItem{
id: id,
ll: list.New(),
num: maxTSCacheNum,
lm: make(map[string]TSItem),
}
}
func (tcCacheItem *TSCacheItem) ID() string {
return tcCacheItem.id
}
// TODO: found data race, fix it
func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) {
var seq int
var getSeq bool
var maxDuration int
m3u8body := bytes.NewBuffer(nil)
for e := tcCacheItem.ll.Front(); e != nil; e = e.Next() {
key := e.Value.(string)
v, ok := tcCacheItem.lm[key]
if ok {
if v.Duration > maxDuration {
maxDuration = v.Duration
}
if !getSeq {
getSeq = true
seq = v.SeqNum
}
fmt.Fprintf(m3u8body, "#EXTINF:%.3f,\n%s\n", float64(v.Duration)/float64(1000), v.Name)
}
}
w := bytes.NewBuffer(nil)
fmt.Fprintf(w,
"#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:NO\n#EXT-X-TARGETDURATION:%d\n#EXT-X-MEDIA-SEQUENCE:%d\n\n",
maxDuration/1000+1, seq)
w.Write(m3u8body.Bytes())
return w.Bytes(), nil
}
func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) {
if tcCacheItem.ll.Len() == tcCacheItem.num {
e := tcCacheItem.ll.Front()
tcCacheItem.ll.Remove(e)
k := e.Value.(string)
delete(tcCacheItem.lm, k)
}
tcCacheItem.lm[key] = item
tcCacheItem.ll.PushBack(key)
}
func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) {
item, ok := tcCacheItem.lm[key]
if !ok {
return item, ErrNoKey
}
return item, nil
}
================================================
FILE: protocol/hls/hls.go
================================================
package hls
import (
"fmt"
"net"
"net/http"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/av"
log "github.com/sirupsen/logrus"
)
const (
duration = 3000
)
var (
ErrNoPublisher = fmt.Errorf("no publisher")
ErrInvalidReq = fmt.Errorf("invalid req url path")
ErrNoSupportVideoCodec = fmt.Errorf("no support video codec")
ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec")
)
var crossdomainxml = []byte(`
`)
type Server struct {
listener net.Listener
conns *sync.Map
}
func NewServer() *Server {
ret := &Server{
conns: &sync.Map{},
}
go ret.checkStop()
return ret
}
func (server *Server) Serve(listener net.Listener) error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
server.handle(w, r)
})
server.listener = listener
if configure.Config.GetBool("use_hls_https") {
http.ServeTLS(listener, mux, "server.crt", "server.key")
} else {
http.Serve(listener, mux)
}
return nil
}
func (server *Server) GetWriter(info av.Info) av.WriteCloser {
var s *Source
v, ok := server.conns.Load(info.Key)
if !ok {
log.Debug("new hls source")
s = NewSource(info)
server.conns.Store(info.Key, s)
} else {
s = v.(*Source)
}
return s
}
func (server *Server) getConn(key string) *Source {
v, ok := server.conns.Load(key)
if !ok {
return nil
}
return v.(*Source)
}
func (server *Server) checkStop() {
for {
<-time.After(5 * time.Second)
server.conns.Range(func(key, val interface{}) bool {
v := val.(*Source)
if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") {
log.Debug("check stop and remove: ", v.Info())
server.conns.Delete(key)
}
return true
})
}
}
func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
if path.Base(r.URL.Path) == "crossdomain.xml" {
w.Header().Set("Content-Type", "application/xml")
w.Write(crossdomainxml)
return
}
switch path.Ext(r.URL.Path) {
case ".m3u8":
key, _ := server.parseM3u8(r.URL.Path)
conn := server.getConn(key)
if conn == nil {
http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
return
}
tsCache := conn.GetCacheInc()
if tsCache == nil {
http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
return
}
body, err := tsCache.GenM3U8PlayList()
if err != nil {
log.Debug("GenM3U8PlayList error: ", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Content-Type", "application/x-mpegURL")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.Write(body)
case ".ts":
key, _ := server.parseTs(r.URL.Path)
conn := server.getConn(key)
if conn == nil {
http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
return
}
tsCache := conn.GetCacheInc()
item, err := tsCache.GetItem(r.URL.Path)
if err != nil {
log.Debug("GetItem error: ", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "video/mp2ts")
w.Header().Set("Content-Length", strconv.Itoa(len(item.Data)))
w.Write(item.Data)
}
}
func (server *Server) parseM3u8(pathstr string) (key string, err error) {
pathstr = strings.TrimLeft(pathstr, "/")
key = strings.Split(pathstr, path.Ext(pathstr))[0]
return
}
func (server *Server) parseTs(pathstr string) (key string, err error) {
pathstr = strings.TrimLeft(pathstr, "/")
paths := strings.SplitN(pathstr, "/", 3)
if len(paths) != 3 {
err = fmt.Errorf("invalid path=%s", pathstr)
return
}
key = paths[0] + "/" + paths[1]
return
}
================================================
FILE: protocol/hls/item.go
================================================
package hls
type TSItem struct {
Name string
SeqNum int
Duration int
Data []byte
}
func NewTSItem(name string, duration, seqNum int, b []byte) TSItem {
var item TSItem
item.Name = name
item.SeqNum = seqNum
item.Duration = duration
item.Data = make([]byte, len(b))
copy(item.Data, b)
return item
}
================================================
FILE: protocol/hls/source.go
================================================
package hls
import (
"bytes"
"fmt"
"github.com/gwuhaolin/livego/configure"
"time"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/container/flv"
"github.com/gwuhaolin/livego/container/ts"
"github.com/gwuhaolin/livego/parser"
log "github.com/sirupsen/logrus"
)
const (
videoHZ = 90000
aacSampleLen = 1024
maxQueueNum = 512
h264_default_hz uint64 = 90
)
type Source struct {
av.RWBaser
seq int
info av.Info
bwriter *bytes.Buffer
btswriter *bytes.Buffer
demuxer *flv.Demuxer
muxer *ts.Muxer
pts, dts uint64
stat *status
align *align
cache *audioCache
tsCache *TSCacheItem
tsparser *parser.CodecParser
closed bool
packetQueue chan *av.Packet
}
func NewSource(info av.Info) *Source {
info.Inter = true
s := &Source{
info: info,
align: &align{},
stat: newStatus(),
RWBaser: av.NewRWBaser(time.Second * 10),
cache: newAudioCache(),
demuxer: flv.NewDemuxer(),
muxer: ts.NewMuxer(),
tsCache: NewTSCacheItem(info.Key),
tsparser: parser.NewCodecParser(),
bwriter: bytes.NewBuffer(make([]byte, 100*1024)),
packetQueue: make(chan *av.Packet, maxQueueNum),
}
go func() {
err := s.SendPacket()
if err != nil {
log.Debug("send packet error: ", err)
s.closed = true
}
}()
return s
}
func (source *Source) GetCacheInc() *TSCacheItem {
return source.tsCache
}
func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue
// try to don't drop audio
if ok && tmpPkt.IsAudio {
if len(pktQue) > maxQueueNum-2 {
<-pktQue
} else {
pktQue <- tmpPkt
}
}
if ok && tmpPkt.IsVideo {
videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)
// dont't drop sps config and dont't drop key frame
if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {
pktQue <- tmpPkt
}
if len(pktQue) > maxQueueNum-10 {
<-pktQue
}
}
}
log.Debug("packet queue len: ", len(pktQue))
}
func (source *Source) Write(p *av.Packet) (err error) {
err = nil
if source.closed {
err = fmt.Errorf("hls source closed")
return
}
source.SetPreTime()
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("hls source has already been closed:%v", e)
}
}()
if len(source.packetQueue) >= maxQueueNum-24 {
source.DropPacket(source.packetQueue, source.info)
} else {
if !source.closed {
source.packetQueue <- p
}
}
return
}
func (source *Source) SendPacket() error {
defer func() {
log.Debugf("[%v] hls sender stop", source.info)
if r := recover(); r != nil {
log.Warning("hls SendPacket panic: ", r)
}
}()
log.Debugf("[%v] hls sender start", source.info)
for {
if source.closed {
return fmt.Errorf("closed")
}
p, ok := <-source.packetQueue
if ok {
if p.IsMetadata {
continue
}
err := source.demuxer.Demux(p)
if err == flv.ErrAvcEndSEQ {
log.Warning(err)
continue
} else {
if err != nil {
log.Warning(err)
return err
}
}
compositionTime, isSeq, err := source.parse(p)
if err != nil {
log.Warning(err)
}
if err != nil || isSeq {
continue
}
if source.btswriter != nil {
source.stat.update(p.IsVideo, p.TimeStamp)
source.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime))
source.tsMux(p)
}
} else {
return fmt.Errorf("closed")
}
}
}
func (source *Source) Info() (ret av.Info) {
return source.info
}
func (source *Source) cleanup() {
close(source.packetQueue)
source.bwriter = nil
source.btswriter = nil
source.cache = nil
source.tsCache = nil
}
func (source *Source) Close(err error) {
log.Debug("hls source closed: ", source.info)
if !source.closed && !configure.Config.GetBool("hls_keep_after_end") {
source.cleanup()
}
source.closed = true
}
func (source *Source) cut() {
newf := true
if source.btswriter == nil {
source.btswriter = bytes.NewBuffer(nil)
} else if source.btswriter != nil && source.stat.durationMs() >= duration {
source.flushAudio()
source.seq++
filename := fmt.Sprintf("/%s/%d.ts", source.info.Key, time.Now().Unix())
item := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes())
source.tsCache.SetItem(filename, item)
source.btswriter.Reset()
source.stat.resetAndNew()
} else {
newf = false
}
if newf {
source.btswriter.Write(source.muxer.PAT())
source.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true))
}
}
func (source *Source) parse(p *av.Packet) (int32, bool, error) {
var compositionTime int32
var ah av.AudioPacketHeader
var vh av.VideoPacketHeader
if p.IsVideo {
vh = p.Header.(av.VideoPacketHeader)
if vh.CodecID() != av.VIDEO_H264 {
return compositionTime, false, ErrNoSupportVideoCodec
}
compositionTime = vh.CompositionTime()
if vh.IsKeyFrame() && vh.IsSeq() {
return compositionTime, true, source.tsparser.Parse(p, source.bwriter)
}
} else {
ah = p.Header.(av.AudioPacketHeader)
if ah.SoundFormat() != av.SOUND_AAC {
return compositionTime, false, ErrNoSupportAudioCodec
}
if ah.AACPacketType() == av.AAC_SEQHDR {
return compositionTime, true, source.tsparser.Parse(p, source.bwriter)
}
}
source.bwriter.Reset()
if err := source.tsparser.Parse(p, source.bwriter); err != nil {
return compositionTime, false, err
}
p.Data = source.bwriter.Bytes()
if p.IsVideo && vh.IsKeyFrame() {
source.cut()
}
return compositionTime, false, nil
}
func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) {
source.dts = uint64(ts) * h264_default_hz
if isVideo {
source.pts = source.dts + uint64(compositionTs)*h264_default_hz
} else {
sampleRate, _ := source.tsparser.SampleRate()
source.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate))
source.pts = source.dts
}
}
func (source *Source) flushAudio() error {
return source.muxAudio(1)
}
func (source *Source) muxAudio(limit byte) error {
if source.cache.CacheNum() < limit {
return nil
}
var p av.Packet
_, pts, buf := source.cache.GetFrame()
p.Data = buf
p.TimeStamp = uint32(pts / h264_default_hz)
return source.muxer.Mux(&p, source.btswriter)
}
func (source *Source) tsMux(p *av.Packet) error {
if p.IsVideo {
return source.muxer.Mux(p, source.btswriter)
} else {
source.cache.Cache(p.Data, source.pts)
return source.muxAudio(cache_max_frames)
}
}
================================================
FILE: protocol/hls/status.go
================================================
package hls
import "time"
type status struct {
hasVideo bool
seqId int64
createdAt time.Time
segBeginAt time.Time
hasSetFirstTs bool
firstTimestamp int64
lastTimestamp int64
}
func newStatus() *status {
return &status{
seqId: 0,
hasSetFirstTs: false,
segBeginAt: time.Now(),
}
}
func (t *status) update(isVideo bool, timestamp uint32) {
if isVideo {
t.hasVideo = true
}
if !t.hasSetFirstTs {
t.hasSetFirstTs = true
t.firstTimestamp = int64(timestamp)
}
t.lastTimestamp = int64(timestamp)
}
func (t *status) resetAndNew() {
t.seqId++
t.hasVideo = false
t.createdAt = time.Now()
t.hasSetFirstTs = false
}
func (t *status) durationMs() int64 {
return t.lastTimestamp - t.firstTimestamp
}
================================================
FILE: protocol/httpflv/server.go
================================================
package httpflv
import (
"encoding/json"
"net"
"net/http"
"strings"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/rtmp"
log "github.com/sirupsen/logrus"
)
type Server struct {
handler av.Handler
}
type stream struct {
Key string `json:"key"`
Id string `json:"id"`
}
type streams struct {
Publishers []stream `json:"publishers"`
Players []stream `json:"players"`
}
func NewServer(h av.Handler) *Server {
return &Server{
handler: h,
}
}
func (server *Server) Serve(l net.Listener) error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
server.handleConn(w, r)
})
mux.HandleFunc("/streams", func(w http.ResponseWriter, r *http.Request) {
server.getStream(w, r)
})
if err := http.Serve(l, mux); err != nil {
return err
}
return nil
}
// 获取发布和播放器的信息
func (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *streams {
rtmpStream := server.handler.(*rtmp.RtmpStream)
if rtmpStream == nil {
return nil
}
msgs := new(streams)
rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
if s, ok := val.(*rtmp.Stream); ok {
if s.GetReader() != nil {
msg := stream{key.(string), s.GetReader().Info().UID}
msgs.Publishers = append(msgs.Publishers, msg)
}
}
return true
})
rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
ws := val.(*rtmp.Stream).GetWs()
ws.Range(func(k, v interface{}) bool {
if pw, ok := v.(*rtmp.PackWriterCloser); ok {
if pw.GetWriter() != nil {
msg := stream{key.(string), pw.GetWriter().Info().UID}
msgs.Players = append(msgs.Players, msg)
}
}
return true
})
return true
})
return msgs
}
func (server *Server) getStream(w http.ResponseWriter, r *http.Request) {
msgs := server.getStreams(w, r)
if msgs == nil {
return
}
resp, _ := json.Marshal(msgs)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Error("http flv handleConn panic: ", r)
}
}()
url := r.URL.String()
u := r.URL.Path
if pos := strings.LastIndex(u, "."); pos < 0 || u[pos:] != ".flv" {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv")
paths := strings.SplitN(path, "/", 2)
log.Debug("url:", u, "path:", path, "paths:", paths)
if len(paths) != 2 {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
// 判断视屏流是否发布,如果没有发布,直接返回404
msgs := server.getStreams(w, r)
if msgs == nil || len(msgs.Publishers) == 0 {
http.Error(w, "invalid path", http.StatusNotFound)
return
} else {
include := false
for _, item := range msgs.Publishers {
if item.Key == path {
include = true
break
}
}
if include == false {
http.Error(w, "invalid path", http.StatusNotFound)
return
}
}
w.Header().Set("Access-Control-Allow-Origin", "*")
writer := NewFLVWriter(paths[0], paths[1], url, w)
server.handler.HandleWriter(writer)
writer.Wait()
}
================================================
FILE: protocol/httpflv/writer.go
================================================
package httpflv
import (
"fmt"
"net/http"
"time"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/uid"
log "github.com/sirupsen/logrus"
)
const (
headerLen = 11
maxQueueNum = 1024
)
type FLVWriter struct {
Uid string
av.RWBaser
app, title, url string
buf []byte
closed bool
closedChan chan struct{}
ctx http.ResponseWriter
packetQueue chan *av.Packet
}
func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter {
ret := &FLVWriter{
Uid: uid.NewId(),
app: app,
title: title,
url: url,
ctx: ctx,
RWBaser: av.NewRWBaser(time.Second * 10),
closedChan: make(chan struct{}),
buf: make([]byte, headerLen),
packetQueue: make(chan *av.Packet, maxQueueNum),
}
if _, err := ret.ctx.Write([]byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}); err != nil {
log.Errorf("Error on response writer")
ret.closed = true
}
pio.PutI32BE(ret.buf[:4], 0)
if _, err := ret.ctx.Write(ret.buf[:4]); err != nil {
log.Errorf("Error on response writer")
ret.closed = true
}
go func() {
err := ret.SendPacket()
if err != nil {
log.Debug("SendPacket error: ", err)
ret.closed = true
}
}()
return ret
}
func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue
if ok && tmpPkt.IsVideo {
videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)
// dont't drop sps config and dont't drop key frame
if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {
log.Debug("insert keyframe to queue")
pktQue <- tmpPkt
}
if len(pktQue) > maxQueueNum-10 {
<-pktQue
}
// drop other packet
<-pktQue
}
// try to don't drop audio
if ok && tmpPkt.IsAudio {
log.Debug("insert audio to queue")
pktQue <- tmpPkt
}
}
log.Debug("packet queue len: ", len(pktQue))
}
func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) {
err = nil
if flvWriter.closed {
err = fmt.Errorf("flvwrite source closed")
return
}
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("FLVWriter has already been closed:%v", e)
}
}()
if len(flvWriter.packetQueue) >= maxQueueNum-24 {
flvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info())
} else {
flvWriter.packetQueue <- p
}
return
}
func (flvWriter *FLVWriter) SendPacket() error {
for {
p, ok := <-flvWriter.packetQueue
if ok {
flvWriter.RWBaser.SetPreTime()
h := flvWriter.buf[:headerLen]
typeID := av.TAG_VIDEO
if !p.IsVideo {
if p.IsMetadata {
var err error
typeID = av.TAG_SCRIPTDATAAMF0
p.Data, err = amf.MetaDataReform(p.Data, amf.DEL)
if err != nil {
return err
}
} else {
typeID = av.TAG_AUDIO
}
}
dataLen := len(p.Data)
timestamp := p.TimeStamp
timestamp += flvWriter.BaseTimeStamp()
flvWriter.RWBaser.RecTimeStamp(timestamp, uint32(typeID))
preDataLen := dataLen + headerLen
timestampbase := timestamp & 0xffffff
timestampExt := timestamp >> 24 & 0xff
pio.PutU8(h[0:1], uint8(typeID))
pio.PutI24BE(h[1:4], int32(dataLen))
pio.PutI24BE(h[4:7], int32(timestampbase))
pio.PutU8(h[7:8], uint8(timestampExt))
if _, err := flvWriter.ctx.Write(h); err != nil {
return err
}
if _, err := flvWriter.ctx.Write(p.Data); err != nil {
return err
}
pio.PutI32BE(h[:4], int32(preDataLen))
if _, err := flvWriter.ctx.Write(h[:4]); err != nil {
return err
}
} else {
return fmt.Errorf("closed")
}
}
}
func (flvWriter *FLVWriter) Wait() {
select {
case <-flvWriter.closedChan:
return
}
}
func (flvWriter *FLVWriter) Close(error) {
log.Debug("http flv closed")
if !flvWriter.closed {
close(flvWriter.packetQueue)
close(flvWriter.closedChan)
}
flvWriter.closed = true
}
func (flvWriter *FLVWriter) Info() (ret av.Info) {
ret.UID = flvWriter.Uid
ret.URL = flvWriter.url
ret.Key = flvWriter.app + "/" + flvWriter.title
ret.Inter = true
return
}
================================================
FILE: protocol/rtmp/cache/cache.go
================================================
package cache
import (
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
)
type Cache struct {
gop *GopCache
videoSeq *SpecialCache
audioSeq *SpecialCache
metadata *SpecialCache
}
func NewCache() *Cache {
return &Cache{
gop: NewGopCache(configure.Config.GetInt("gop_num")),
videoSeq: NewSpecialCache(),
audioSeq: NewSpecialCache(),
metadata: NewSpecialCache(),
}
}
func (cache *Cache) Write(p av.Packet) {
if p.IsMetadata {
cache.metadata.Write(&p)
return
} else {
if !p.IsVideo {
ah, ok := p.Header.(av.AudioPacketHeader)
if ok {
if ah.SoundFormat() == av.SOUND_AAC &&
ah.AACPacketType() == av.AAC_SEQHDR {
cache.audioSeq.Write(&p)
return
} else {
return
}
}
} else {
vh, ok := p.Header.(av.VideoPacketHeader)
if ok {
if vh.IsSeq() {
cache.videoSeq.Write(&p)
return
}
} else {
return
}
}
}
cache.gop.Write(&p)
}
func (cache *Cache) Send(w av.WriteCloser) error {
if err := cache.metadata.Send(w); err != nil {
return err
}
if err := cache.videoSeq.Send(w); err != nil {
return err
}
if err := cache.audioSeq.Send(w); err != nil {
return err
}
if err := cache.gop.Send(w); err != nil {
return err
}
return nil
}
================================================
FILE: protocol/rtmp/cache/gop.go
================================================
package cache
import (
"fmt"
"github.com/gwuhaolin/livego/av"
)
var (
maxGOPCap int = 1024
ErrGopTooBig = fmt.Errorf("gop to big")
)
type array struct {
index int
packets []*av.Packet
}
func newArray() *array {
ret := &array{
index: 0,
packets: make([]*av.Packet, 0, maxGOPCap),
}
return ret
}
func (array *array) reset() {
array.index = 0
array.packets = array.packets[:0]
}
func (array *array) write(packet *av.Packet) error {
if array.index >= maxGOPCap {
return ErrGopTooBig
}
array.packets = append(array.packets, packet)
array.index++
return nil
}
func (array *array) send(w av.WriteCloser) error {
var err error
for i := 0; i < array.index; i++ {
packet := array.packets[i]
if err = w.Write(packet); err != nil {
return err
}
}
return err
}
type GopCache struct {
start bool
num int
count int
nextindex int
gops []*array
}
func NewGopCache(num int) *GopCache {
return &GopCache{
count: num,
gops: make([]*array, num),
}
}
func (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool) error {
var ginc *array
if startNew {
ginc = gopCache.gops[gopCache.nextindex]
if ginc == nil {
ginc = newArray()
gopCache.num++
gopCache.gops[gopCache.nextindex] = ginc
} else {
ginc.reset()
}
gopCache.nextindex = (gopCache.nextindex + 1) % gopCache.count
} else {
ginc = gopCache.gops[(gopCache.nextindex+1)%gopCache.count]
}
ginc.write(chunk)
return nil
}
func (gopCache *GopCache) Write(p *av.Packet) {
var ok bool
if p.IsVideo {
vh := p.Header.(av.VideoPacketHeader)
if vh.IsKeyFrame() && !vh.IsSeq() {
ok = true
}
}
if ok || gopCache.start {
gopCache.start = true
gopCache.writeToArray(p, ok)
}
}
func (gopCache *GopCache) sendTo(w av.WriteCloser) error {
var err error
pos := (gopCache.nextindex + 1) % gopCache.count
for i := 0; i < gopCache.num; i++ {
index := (pos - gopCache.num + 1) + i
if index < 0 {
index += gopCache.count
}
g := gopCache.gops[index]
err = g.send(w)
if err != nil {
return err
}
}
return nil
}
func (gopCache *GopCache) Send(w av.WriteCloser) error {
return gopCache.sendTo(w)
}
================================================
FILE: protocol/rtmp/cache/special.go
================================================
package cache
import (
"bytes"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
)
const (
SetDataFrame string = "@setDataFrame"
OnMetaData string = "onMetaData"
)
var setFrameFrame []byte
func init() {
b := bytes.NewBuffer(nil)
encoder := &amf.Encoder{}
if _, err := encoder.Encode(b, SetDataFrame, amf.AMF0); err != nil {
log.Fatal(err)
}
setFrameFrame = b.Bytes()
}
type SpecialCache struct {
full bool
p *av.Packet
}
func NewSpecialCache() *SpecialCache {
return &SpecialCache{}
}
func (specialCache *SpecialCache) Write(p *av.Packet) {
specialCache.p = p
specialCache.full = true
}
func (specialCache *SpecialCache) Send(w av.WriteCloser) error {
if !specialCache.full {
return nil
}
// demux in hls will change p.Data, only send a copy here
newPacket := *specialCache.p
return w.Write(&newPacket)
}
================================================
FILE: protocol/rtmp/core/chunk_stream.go
================================================
package core
import (
"encoding/binary"
"fmt"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/utils/pool"
)
type ChunkStream struct {
Format uint32
CSID uint32
Timestamp uint32
Length uint32
TypeID uint32
StreamID uint32
timeDelta uint32
exted bool
index uint32
remain uint32
got bool
tmpFromat uint32
Data []byte
}
func (chunkStream *ChunkStream) full() bool {
return chunkStream.got
}
func (chunkStream *ChunkStream) new(pool *pool.Pool) {
chunkStream.got = false
chunkStream.index = 0
chunkStream.remain = chunkStream.Length
chunkStream.Data = pool.Get(int(chunkStream.Length))
}
func (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error {
//Chunk Basic Header
h := chunkStream.Format << 6
switch {
case chunkStream.CSID < 64:
h |= chunkStream.CSID
w.WriteUintBE(h, 1)
case chunkStream.CSID-64 < 256:
h |= 0
w.WriteUintBE(h, 1)
w.WriteUintLE(chunkStream.CSID-64, 1)
case chunkStream.CSID-64 < 65536:
h |= 1
w.WriteUintBE(h, 1)
w.WriteUintLE(chunkStream.CSID-64, 2)
}
//Chunk Message Header
ts := chunkStream.Timestamp
if chunkStream.Format == 3 {
goto END
}
if chunkStream.Timestamp > 0xffffff {
ts = 0xffffff
}
w.WriteUintBE(ts, 3)
if chunkStream.Format == 2 {
goto END
}
if chunkStream.Length > 0xffffff {
return fmt.Errorf("length=%d", chunkStream.Length)
}
w.WriteUintBE(chunkStream.Length, 3)
w.WriteUintBE(chunkStream.TypeID, 1)
if chunkStream.Format == 1 {
goto END
}
w.WriteUintLE(chunkStream.StreamID, 4)
END:
//Extended Timestamp
if ts >= 0xffffff {
w.WriteUintBE(chunkStream.Timestamp, 4)
}
return w.WriteError()
}
func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize int) error {
if chunkStream.TypeID == av.TAG_AUDIO {
chunkStream.CSID = 4
} else if chunkStream.TypeID == av.TAG_VIDEO ||
chunkStream.TypeID == av.TAG_SCRIPTDATAAMF0 ||
chunkStream.TypeID == av.TAG_SCRIPTDATAAMF3 {
chunkStream.CSID = 6
}
totalLen := uint32(0)
numChunks := (chunkStream.Length / uint32(chunkSize))
for i := uint32(0); i <= numChunks; i++ {
if totalLen == chunkStream.Length {
break
}
if i == 0 {
chunkStream.Format = uint32(0)
} else {
chunkStream.Format = uint32(3)
}
if err := chunkStream.writeHeader(w); err != nil {
return err
}
inc := uint32(chunkSize)
start := uint32(i) * uint32(chunkSize)
if uint32(len(chunkStream.Data))-start <= inc {
inc = uint32(len(chunkStream.Data)) - start
}
totalLen += inc
end := start + inc
buf := chunkStream.Data[start:end]
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error {
if chunkStream.remain != 0 && chunkStream.tmpFromat != 3 {
return fmt.Errorf("invalid remain = %d", chunkStream.remain)
}
switch chunkStream.CSID {
case 0:
id, _ := r.ReadUintLE(1)
chunkStream.CSID = id + 64
case 1:
id, _ := r.ReadUintLE(2)
chunkStream.CSID = id + 64
}
switch chunkStream.tmpFromat {
case 0:
chunkStream.Format = chunkStream.tmpFromat
chunkStream.Timestamp, _ = r.ReadUintBE(3)
chunkStream.Length, _ = r.ReadUintBE(3)
chunkStream.TypeID, _ = r.ReadUintBE(1)
chunkStream.StreamID, _ = r.ReadUintLE(4)
if chunkStream.Timestamp == 0xffffff {
chunkStream.Timestamp, _ = r.ReadUintBE(4)
chunkStream.exted = true
} else {
chunkStream.exted = false
}
chunkStream.new(pool)
case 1:
chunkStream.Format = chunkStream.tmpFromat
timeStamp, _ := r.ReadUintBE(3)
chunkStream.Length, _ = r.ReadUintBE(3)
chunkStream.TypeID, _ = r.ReadUintBE(1)
if timeStamp == 0xffffff {
timeStamp, _ = r.ReadUintBE(4)
chunkStream.exted = true
} else {
chunkStream.exted = false
}
chunkStream.timeDelta = timeStamp
chunkStream.Timestamp += timeStamp
chunkStream.new(pool)
case 2:
chunkStream.Format = chunkStream.tmpFromat
timeStamp, _ := r.ReadUintBE(3)
if timeStamp == 0xffffff {
timeStamp, _ = r.ReadUintBE(4)
chunkStream.exted = true
} else {
chunkStream.exted = false
}
chunkStream.timeDelta = timeStamp
chunkStream.Timestamp += timeStamp
chunkStream.new(pool)
case 3:
if chunkStream.remain == 0 {
switch chunkStream.Format {
case 0:
if chunkStream.exted {
timestamp, _ := r.ReadUintBE(4)
chunkStream.Timestamp = timestamp
}
case 1, 2:
var timedet uint32
if chunkStream.exted {
timedet, _ = r.ReadUintBE(4)
} else {
timedet = chunkStream.timeDelta
}
chunkStream.Timestamp += timedet
}
chunkStream.new(pool)
} else {
if chunkStream.exted {
b, err := r.Peek(4)
if err != nil {
return err
}
tmpts := binary.BigEndian.Uint32(b)
if tmpts == chunkStream.Timestamp {
r.Discard(4)
}
}
}
default:
return fmt.Errorf("invalid format=%d", chunkStream.Format)
}
size := int(chunkStream.remain)
if size > int(chunkSize) {
size = int(chunkSize)
}
buf := chunkStream.Data[chunkStream.index : chunkStream.index+uint32(size)]
if _, err := r.Read(buf); err != nil {
return err
}
chunkStream.index += uint32(size)
chunkStream.remain -= uint32(size)
if chunkStream.remain == 0 {
chunkStream.got = true
}
return r.readError
}
================================================
FILE: protocol/rtmp/core/chunk_stream_test.go
================================================
package core
import (
"bytes"
"testing"
"github.com/gwuhaolin/livego/utils/pool"
"github.com/stretchr/testify/assert"
)
func TestChunkRead1(t *testing.T) {
at := assert.New(t)
data := []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,
}
data1 := make([]byte, 128)
data2 := make([]byte, 51)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data2...)
rw := NewReadWriter(bytes.NewBuffer(data), 1024)
chunkinc := &ChunkStream{}
for {
h, _ := rw.ReadUintBE(1)
chunkinc.tmpFromat = h >> 6
chunkinc.CSID = h & 0x3f
chunkinc.readChunk(rw, 128, pool.NewPool())
if chunkinc.remain == 0 {
break
}
}
at.Equal(int(chunkinc.Length), 307)
at.Equal(int(chunkinc.TypeID), 9)
at.Equal(int(chunkinc.StreamID), 1)
at.Equal(len(chunkinc.Data), 307)
at.Equal(int(chunkinc.remain), 0)
data = []byte{
0x06, 0xff, 0xff, 0xff, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
}
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, []byte{0x00, 0x00, 0x00, 0x05}...)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data2...)
rw = NewReadWriter(bytes.NewBuffer(data), 1024)
chunkinc = &ChunkStream{}
h, _ := rw.ReadUintBE(1)
chunkinc.tmpFromat = h >> 6
chunkinc.CSID = h & 0x3f
chunkinc.readChunk(rw, 128, pool.NewPool())
h, _ = rw.ReadUintBE(1)
chunkinc.tmpFromat = h >> 6
chunkinc.CSID = h & 0x3f
chunkinc.readChunk(rw, 128, pool.NewPool())
h, _ = rw.ReadUintBE(1)
chunkinc.tmpFromat = h >> 6
chunkinc.CSID = h & 0x3f
chunkinc.readChunk(rw, 128, pool.NewPool())
at.Equal(int(chunkinc.Length), 307)
at.Equal(int(chunkinc.TypeID), 9)
at.Equal(int(chunkinc.StreamID), 1)
at.Equal(len(chunkinc.Data), 307)
at.Equal(chunkinc.exted, true)
at.Equal(int(chunkinc.Timestamp), 5)
at.Equal(int(chunkinc.remain), 0)
}
func TestWriteChunk(t *testing.T) {
at := assert.New(t)
chunkinc := &ChunkStream{}
chunkinc.Length = 307
chunkinc.TypeID = 9
chunkinc.CSID = 4
chunkinc.Timestamp = 40
chunkinc.Data = make([]byte, 307)
bf := bytes.NewBuffer(nil)
w := NewReadWriter(bf, 1024)
err := chunkinc.writeChunk(w, 128)
w.Flush()
at.Equal(err, nil)
at.Equal(len(bf.Bytes()), 321)
}
================================================
FILE: protocol/rtmp/core/conn.go
================================================
package core
import (
"encoding/binary"
"net"
"time"
"github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/pool"
)
const (
_ = iota
idSetChunkSize
idAbortMessage
idAck
idUserControlMessages
idWindowAckSize
idSetPeerBandwidth
)
type Conn struct {
net.Conn
chunkSize uint32
remoteChunkSize uint32
windowAckSize uint32
remoteWindowAckSize uint32
received uint32
ackReceived uint32
rw *ReadWriter
pool *pool.Pool
chunks map[uint32]ChunkStream
}
func NewConn(c net.Conn, bufferSize int) *Conn {
return &Conn{
Conn: c,
chunkSize: 128,
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
pool: pool.NewPool(),
rw: NewReadWriter(c, bufferSize),
chunks: make(map[uint32]ChunkStream),
}
}
func (conn *Conn) Read(c *ChunkStream) error {
for {
h, _ := conn.rw.ReadUintBE(1)
// if err != nil {
// log.Println("read from conn error: ", err)
// return err
// }
format := h >> 6
csid := h & 0x3f
cs, ok := conn.chunks[csid]
if !ok {
cs = ChunkStream{}
conn.chunks[csid] = cs
}
cs.tmpFromat = format
cs.CSID = csid
err := cs.readChunk(conn.rw, conn.remoteChunkSize, conn.pool)
if err != nil {
return err
}
conn.chunks[csid] = cs
if cs.full() {
*c = cs
break
}
}
conn.handleControlMsg(c)
conn.ack(c.Length)
return nil
}
func (conn *Conn) Write(c *ChunkStream) error {
if c.TypeID == idSetChunkSize {
conn.chunkSize = binary.BigEndian.Uint32(c.Data)
}
return c.writeChunk(conn.rw, int(conn.chunkSize))
}
func (conn *Conn) Flush() error {
return conn.rw.Flush()
}
func (conn *Conn) Close() error {
return conn.Conn.Close()
}
func (conn *Conn) RemoteAddr() net.Addr {
return conn.Conn.RemoteAddr()
}
func (conn *Conn) LocalAddr() net.Addr {
return conn.Conn.LocalAddr()
}
func (conn *Conn) SetDeadline(t time.Time) error {
return conn.Conn.SetDeadline(t)
}
func (conn *Conn) NewAck(size uint32) ChunkStream {
return initControlMsg(idAck, 4, size)
}
func (conn *Conn) NewSetChunkSize(size uint32) ChunkStream {
return initControlMsg(idSetChunkSize, 4, size)
}
func (conn *Conn) NewWindowAckSize(size uint32) ChunkStream {
return initControlMsg(idWindowAckSize, 4, size)
}
func (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream {
ret := initControlMsg(idSetPeerBandwidth, 5, size)
ret.Data[4] = 2
return ret
}
func (conn *Conn) handleControlMsg(c *ChunkStream) {
if c.TypeID == idSetChunkSize {
conn.remoteChunkSize = binary.BigEndian.Uint32(c.Data)
} else if c.TypeID == idWindowAckSize {
conn.remoteWindowAckSize = binary.BigEndian.Uint32(c.Data)
}
}
func (conn *Conn) ack(size uint32) {
conn.received += uint32(size)
conn.ackReceived += uint32(size)
if conn.received >= 0xf0000000 {
conn.received = 0
}
if conn.ackReceived >= conn.remoteWindowAckSize {
cs := conn.NewAck(conn.ackReceived)
cs.writeChunk(conn.rw, int(conn.chunkSize))
conn.ackReceived = 0
}
}
func initControlMsg(id, size, value uint32) ChunkStream {
ret := ChunkStream{
Format: 0,
CSID: 2,
TypeID: id,
StreamID: 0,
Length: size,
Data: make([]byte, size),
}
pio.PutU32BE(ret.Data[:size], value)
return ret
}
const (
streamBegin uint32 = 0
streamEOF uint32 = 1
streamDry uint32 = 2
setBufferLen uint32 = 3
streamIsRecorded uint32 = 4
pingRequest uint32 = 6
pingResponse uint32 = 7
)
/*
+------------------------------+-------------------------
| Event Type ( 2- bytes ) | Event Data
+------------------------------+-------------------------
Pay load for the ‘User Control Message’.
*/
func (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream {
var ret ChunkStream
buflen += 2
ret = ChunkStream{
Format: 0,
CSID: 2,
TypeID: 4,
StreamID: 1,
Length: buflen,
Data: make([]byte, buflen),
}
ret.Data[0] = byte(eventType >> 8 & 0xff)
ret.Data[1] = byte(eventType & 0xff)
return ret
}
func (conn *Conn) SetBegin() {
ret := conn.userControlMsg(streamBegin, 4)
for i := 0; i < 4; i++ {
ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff)
}
conn.Write(&ret)
}
func (conn *Conn) SetRecorded() {
ret := conn.userControlMsg(streamIsRecorded, 4)
for i := 0; i < 4; i++ {
ret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff)
}
conn.Write(&ret)
}
================================================
FILE: protocol/rtmp/core/conn_client.go
================================================
package core
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"math/rand"
"net"
neturl "net/url"
"strings"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
)
var (
respResult = "_result"
respError = "_error"
onStatus = "onStatus"
publishStart = "NetStream.Publish.Start"
playStart = "NetStream.Play.Start"
connectSuccess = "NetConnection.Connect.Success"
onBWDone = "onBWDone"
)
var (
ErrFail = fmt.Errorf("response err")
)
type ConnClient struct {
done bool
transID int
url string
tcurl string
app string
title string
curcmdName string
streamid uint32
isRTMPS bool
conn *Conn
encoder *amf.Encoder
decoder *amf.Decoder
bytesw *bytes.Buffer
}
func NewConnClient() *ConnClient {
return &ConnClient{
transID: 1,
bytesw: bytes.NewBuffer(nil),
encoder: &amf.Encoder{},
decoder: &amf.Decoder{},
}
}
func (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version) (ret []interface{}, err error) {
vs, err := connClient.decoder.DecodeBatch(r, ver)
return vs, err
}
func (connClient *ConnClient) readRespMsg() error {
var err error
var rc ChunkStream
for {
if err = connClient.conn.Read(&rc); err != nil {
return err
}
if err != nil && err != io.EOF {
return err
}
switch rc.TypeID {
case 20, 17:
r := bytes.NewReader(rc.Data)
vs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0)
log.Debugf("readRespMsg: vs=%v", vs)
for k, v := range vs {
switch v.(type) {
case string:
switch connClient.curcmdName {
case cmdConnect, cmdCreateStream:
if v.(string) != respResult {
return fmt.Errorf(v.(string))
}
case cmdPublish:
if v.(string) != onStatus {
return ErrFail
}
}
case float64:
switch connClient.curcmdName {
case cmdConnect, cmdCreateStream:
id := int(v.(float64))
if k == 1 {
if id != connClient.transID {
return ErrFail
}
} else if k == 3 {
connClient.streamid = uint32(id)
}
case cmdPublish:
if int(v.(float64)) != 0 {
return ErrFail
}
}
case amf.Object:
objmap := v.(amf.Object)
switch connClient.curcmdName {
case cmdConnect:
code, ok := objmap["code"]
if ok && code.(string) != connectSuccess {
return ErrFail
}
case cmdPublish:
code, ok := objmap["code"]
if ok && code.(string) != publishStart {
return ErrFail
}
}
}
}
return nil
}
}
}
func (connClient *ConnClient) writeMsg(args ...interface{}) error {
connClient.bytesw.Reset()
for _, v := range args {
if _, err := connClient.encoder.Encode(connClient.bytesw, v, amf.AMF0); err != nil {
return err
}
}
msg := connClient.bytesw.Bytes()
c := ChunkStream{
Format: 0,
CSID: 3,
Timestamp: 0,
TypeID: 20,
StreamID: connClient.streamid,
Length: uint32(len(msg)),
Data: msg,
}
connClient.conn.Write(&c)
return connClient.conn.Flush()
}
func (connClient *ConnClient) writeConnectMsg() error {
event := make(amf.Object)
event["app"] = connClient.app
event["type"] = "nonprivate"
event["flashVer"] = "FMS.3.1"
event["tcUrl"] = connClient.tcurl
connClient.curcmdName = cmdConnect
log.Debugf("writeConnectMsg: connClient.transID=%d, event=%v", connClient.transID, event)
if err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil {
return err
}
return connClient.readRespMsg()
}
func (connClient *ConnClient) writeCreateStreamMsg() error {
connClient.transID++
connClient.curcmdName = cmdCreateStream
log.Debugf("writeCreateStreamMsg: connClient.transID=%d", connClient.transID)
if err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil {
return err
}
for {
err := connClient.readRespMsg()
if err == nil {
return err
}
if err == ErrFail {
log.Debugf("writeCreateStreamMsg readRespMsg err=%v", err)
return err
}
}
}
func (connClient *ConnClient) writePublishMsg() error {
connClient.transID++
connClient.curcmdName = cmdPublish
if err := connClient.writeMsg(cmdPublish, connClient.transID, nil, connClient.title, publishLive); err != nil {
return err
}
return connClient.readRespMsg()
}
func (connClient *ConnClient) writePlayMsg() error {
connClient.transID++
connClient.curcmdName = cmdPlay
log.Debugf("writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v",
connClient.transID, cmdPlay, connClient.title)
if err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil {
return err
}
return connClient.readRespMsg()
}
func (connClient *ConnClient) Start(url string, method string) error {
u, err := neturl.Parse(url)
if err != nil {
return err
}
connClient.url = url
path := strings.TrimLeft(u.Path, "/")
ps := strings.SplitN(path, "/", 2)
if len(ps) != 2 {
return fmt.Errorf("u path err: %s", path)
}
connClient.app = ps[0]
connClient.title = ps[1]
if u.RawQuery != "" {
connClient.title += "?" + u.RawQuery
}
connClient.isRTMPS = strings.HasPrefix(url, "rtmps://")
var port string
if connClient.isRTMPS {
connClient.tcurl = "rtmps://" + u.Host + "/" + connClient.app
port = ":443"
} else {
connClient.tcurl = "rtmp://" + u.Host + "/" + connClient.app
port = ":1935"
}
host := u.Host
localIP := ":0"
var remoteIP string
if strings.Index(host, ":") != -1 {
host, port, err = net.SplitHostPort(host)
if err != nil {
return err
}
port = ":" + port
}
ips, err := net.LookupIP(host)
log.Debugf("ips: %v, host: %v", ips, host)
if err != nil {
log.Warning(err)
return err
}
remoteIP = ips[rand.Intn(len(ips))].String()
if strings.Index(remoteIP, ":") == -1 {
remoteIP += port
}
local, err := net.ResolveTCPAddr("tcp", localIP)
if err != nil {
log.Warning(err)
return err
}
log.Debug("remoteIP: ", remoteIP)
remote, err := net.ResolveTCPAddr("tcp", remoteIP)
if err != nil {
log.Warning(err)
return err
}
var conn net.Conn
if connClient.isRTMPS {
var config tls.Config
if configure.Config.GetBool("enable_tls_verify") {
roots, err := x509.SystemCertPool()
if err != nil {
log.Warning(err)
return err
}
config.RootCAs = roots
} else {
config.InsecureSkipVerify = true
}
conn, err = tls.Dial("tcp", remoteIP, &config)
if err != nil {
log.Warning(err)
return err
}
} else {
conn, err = net.DialTCP("tcp", local, remote)
if err != nil {
log.Warning(err)
return err
}
}
log.Debug("connection:", "local:", conn.LocalAddr(), "remote:", conn.RemoteAddr())
connClient.conn = NewConn(conn, 4*1024)
log.Debug("HandshakeClient....")
if err := connClient.conn.HandshakeClient(); err != nil {
return err
}
log.Debug("writeConnectMsg....")
if err := connClient.writeConnectMsg(); err != nil {
return err
}
log.Debug("writeCreateStreamMsg....")
if err := connClient.writeCreateStreamMsg(); err != nil {
log.Debug("writeCreateStreamMsg error", err)
return err
}
log.Debug("method control:", method, av.PUBLISH, av.PLAY)
if method == av.PUBLISH {
if err := connClient.writePublishMsg(); err != nil {
return err
}
} else if method == av.PLAY {
if err := connClient.writePlayMsg(); err != nil {
return err
}
}
return nil
}
func (connClient *ConnClient) Write(c ChunkStream) error {
if c.TypeID == av.TAG_SCRIPTDATAAMF0 ||
c.TypeID == av.TAG_SCRIPTDATAAMF3 {
var err error
if c.Data, err = amf.MetaDataReform(c.Data, amf.ADD); err != nil {
return err
}
c.Length = uint32(len(c.Data))
}
return connClient.conn.Write(&c)
}
func (connClient *ConnClient) Flush() error {
return connClient.conn.Flush()
}
func (connClient *ConnClient) Read(c *ChunkStream) (err error) {
return connClient.conn.Read(c)
}
func (connClient *ConnClient) GetInfo() (app string, name string, url string) {
app = connClient.app
name = connClient.title
url = connClient.url
return
}
func (connClient *ConnClient) GetStreamId() uint32 {
return connClient.streamid
}
func (connClient *ConnClient) Close(err error) {
connClient.conn.Close()
}
================================================
FILE: protocol/rtmp/core/conn_server.go
================================================
package core
import (
"bytes"
"fmt"
"io"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
)
var (
publishLive = "live"
publishRecord = "record"
publishAppend = "append"
)
var (
ErrReq = fmt.Errorf("req error")
)
var (
cmdConnect = "connect"
cmdFcpublish = "FCPublish"
cmdReleaseStream = "releaseStream"
cmdCreateStream = "createStream"
cmdPublish = "publish"
cmdFCUnpublish = "FCUnpublish"
cmdDeleteStream = "deleteStream"
cmdPlay = "play"
)
type ConnectInfo struct {
App string `amf:"app" json:"app"`
Flashver string `amf:"flashVer" json:"flashVer"`
SwfUrl string `amf:"swfUrl" json:"swfUrl"`
TcUrl string `amf:"tcUrl" json:"tcUrl"`
Fpad bool `amf:"fpad" json:"fpad"`
AudioCodecs int `amf:"audioCodecs" json:"audioCodecs"`
VideoCodecs int `amf:"videoCodecs" json:"videoCodecs"`
VideoFunction int `amf:"videoFunction" json:"videoFunction"`
PageUrl string `amf:"pageUrl" json:"pageUrl"`
ObjectEncoding int `amf:"objectEncoding" json:"objectEncoding"`
}
type ConnectResp struct {
FMSVer string `amf:"fmsVer"`
Capabilities int `amf:"capabilities"`
}
type ConnectEvent struct {
Level string `amf:"level"`
Code string `amf:"code"`
Description string `amf:"description"`
ObjectEncoding int `amf:"objectEncoding"`
}
type PublishInfo struct {
Name string
Type string
}
type ConnServer struct {
done bool
streamID int
isPublisher bool
conn *Conn
transactionID int
ConnInfo ConnectInfo
PublishInfo PublishInfo
decoder *amf.Decoder
encoder *amf.Encoder
bytesw *bytes.Buffer
}
func NewConnServer(conn *Conn) *ConnServer {
return &ConnServer{
conn: conn,
streamID: 1,
bytesw: bytes.NewBuffer(nil),
decoder: &amf.Decoder{},
encoder: &amf.Encoder{},
}
}
func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ...interface{}) error {
connServer.bytesw.Reset()
for _, v := range args {
if _, err := connServer.encoder.Encode(connServer.bytesw, v, amf.AMF0); err != nil {
return err
}
}
msg := connServer.bytesw.Bytes()
c := ChunkStream{
Format: 0,
CSID: csid,
Timestamp: 0,
TypeID: 20,
StreamID: streamID,
Length: uint32(len(msg)),
Data: msg,
}
connServer.conn.Write(&c)
return connServer.conn.Flush()
}
func (connServer *ConnServer) connect(vs []interface{}) error {
for _, v := range vs {
switch v.(type) {
case string:
case float64:
id := int(v.(float64))
if id != 1 {
return ErrReq
}
connServer.transactionID = id
case amf.Object:
obimap := v.(amf.Object)
if app, ok := obimap["app"]; ok {
connServer.ConnInfo.App = app.(string)
}
if flashVer, ok := obimap["flashVer"]; ok {
connServer.ConnInfo.Flashver = flashVer.(string)
}
if tcurl, ok := obimap["tcUrl"]; ok {
connServer.ConnInfo.TcUrl = tcurl.(string)
}
if encoding, ok := obimap["objectEncoding"]; ok {
connServer.ConnInfo.ObjectEncoding = int(encoding.(float64))
}
}
}
return nil
}
func (connServer *ConnServer) releaseStream(vs []interface{}) error {
return nil
}
func (connServer *ConnServer) fcPublish(vs []interface{}) error {
return nil
}
func (connServer *ConnServer) connectResp(cur *ChunkStream) error {
c := connServer.conn.NewWindowAckSize(2500000)
connServer.conn.Write(&c)
c = connServer.conn.NewSetPeerBandwidth(2500000)
connServer.conn.Write(&c)
c = connServer.conn.NewSetChunkSize(uint32(1024))
connServer.conn.Write(&c)
resp := make(amf.Object)
resp["fmsVer"] = "FMS/3,0,1,123"
resp["capabilities"] = 31
event := make(amf.Object)
event["level"] = "status"
event["code"] = "NetConnection.Connect.Success"
event["description"] = "Connection succeeded."
event["objectEncoding"] = connServer.ConnInfo.ObjectEncoding
return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, resp, event)
}
func (connServer *ConnServer) createStream(vs []interface{}) error {
for _, v := range vs {
switch v.(type) {
case string:
case float64:
connServer.transactionID = int(v.(float64))
case amf.Object:
}
}
return nil
}
func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error {
return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, nil, connServer.streamID)
}
func (connServer *ConnServer) publishOrPlay(vs []interface{}) error {
for k, v := range vs {
switch v.(type) {
case string:
if k == 2 {
connServer.PublishInfo.Name = v.(string)
} else if k == 3 {
connServer.PublishInfo.Type = v.(string)
}
case float64:
id := int(v.(float64))
connServer.transactionID = id
case amf.Object:
}
}
return nil
}
func (connServer *ConnServer) publishResp(cur *ChunkStream) error {
event := make(amf.Object)
event["level"] = "status"
event["code"] = "NetStream.Publish.Start"
event["description"] = "Start publishing."
return connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event)
}
func (connServer *ConnServer) playResp(cur *ChunkStream) error {
connServer.conn.SetRecorded()
connServer.conn.SetBegin()
event := make(amf.Object)
event["level"] = "status"
event["code"] = "NetStream.Play.Reset"
event["description"] = "Playing and resetting stream."
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil {
return err
}
event["level"] = "status"
event["code"] = "NetStream.Play.Start"
event["description"] = "Started playing stream."
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil {
return err
}
event["level"] = "status"
event["code"] = "NetStream.Data.Start"
event["description"] = "Started playing stream."
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil {
return err
}
event["level"] = "status"
event["code"] = "NetStream.Play.PublishNotify"
event["description"] = "Started playing notify."
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil {
return err
}
return connServer.conn.Flush()
}
func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
amfType := amf.AMF0
if c.TypeID == 17 {
c.Data = c.Data[1:]
}
r := bytes.NewReader(c.Data)
vs, err := connServer.decoder.DecodeBatch(r, amf.Version(amfType))
if err != nil && err != io.EOF {
return err
}
// log.Debugf("rtmp req: %#v", vs)
switch vs[0].(type) {
case string:
switch vs[0].(string) {
case cmdConnect:
if err = connServer.connect(vs[1:]); err != nil {
return err
}
if err = connServer.connectResp(c); err != nil {
return err
}
case cmdCreateStream:
if err = connServer.createStream(vs[1:]); err != nil {
return err
}
if err = connServer.createStreamResp(c); err != nil {
return err
}
case cmdPublish:
if err = connServer.publishOrPlay(vs[1:]); err != nil {
return err
}
if err = connServer.publishResp(c); err != nil {
return err
}
connServer.done = true
connServer.isPublisher = true
log.Debug("handle publish req done")
case cmdPlay:
if err = connServer.publishOrPlay(vs[1:]); err != nil {
return err
}
if err = connServer.playResp(c); err != nil {
return err
}
connServer.done = true
connServer.isPublisher = false
log.Debug("handle play req done")
case cmdFcpublish:
connServer.fcPublish(vs)
case cmdReleaseStream:
connServer.releaseStream(vs)
case cmdFCUnpublish:
case cmdDeleteStream:
default:
log.Debug("no support command=", vs[0].(string))
}
}
return nil
}
func (connServer *ConnServer) ReadMsg() error {
var c ChunkStream
for {
if err := connServer.conn.Read(&c); err != nil {
return err
}
switch c.TypeID {
case 20, 17:
if err := connServer.handleCmdMsg(&c); err != nil {
return err
}
}
if connServer.done {
break
}
}
return nil
}
func (connServer *ConnServer) IsPublisher() bool {
return connServer.isPublisher
}
func (connServer *ConnServer) Write(c ChunkStream) error {
if c.TypeID == av.TAG_SCRIPTDATAAMF0 ||
c.TypeID == av.TAG_SCRIPTDATAAMF3 {
var err error
if c.Data, err = amf.MetaDataReform(c.Data, amf.DEL); err != nil {
return err
}
c.Length = uint32(len(c.Data))
}
return connServer.conn.Write(&c)
}
func (connServer *ConnServer) Flush() error {
return connServer.conn.Flush()
}
func (connServer *ConnServer) Read(c *ChunkStream) (err error) {
return connServer.conn.Read(c)
}
func (connServer *ConnServer) GetInfo() (app string, name string, url string) {
app = connServer.ConnInfo.App
name = connServer.PublishInfo.Name
url = connServer.ConnInfo.TcUrl + "/" + connServer.PublishInfo.Name
return
}
func (connServer *ConnServer) Close(err error) {
connServer.conn.Close()
}
================================================
FILE: protocol/rtmp/core/conn_test.go
================================================
package core
import (
"bytes"
"io"
"testing"
"github.com/gwuhaolin/livego/utils/pool"
"github.com/stretchr/testify/assert"
)
func TestConnReadNormal(t *testing.T) {
at := assert.New(t)
data := []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,
}
data1 := make([]byte, 128)
data2 := make([]byte, 51)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data2...)
conn := &Conn{
pool: pool.NewPool(),
rw: NewReadWriter(bytes.NewBuffer(data), 1024),
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
chunks: make(map[uint32]ChunkStream),
}
var c ChunkStream
err := conn.Read(&c)
at.Equal(err, nil)
at.Equal(int(c.CSID), 6)
at.Equal(int(c.Length), 307)
at.Equal(int(c.TypeID), 9)
}
//交叉读音视频数据
func TestConnCrossReading(t *testing.T) {
at := assert.New(t)
data1 := make([]byte, 128)
data2 := make([]byte, 51)
videoData := []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,
}
audioData := []byte{
0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x08, 0x01, 0x00, 0x00, 0x00,
}
//video 1
videoData = append(videoData, data1...)
//video 2
videoData = append(videoData, 0xc6)
videoData = append(videoData, data1...)
//audio 1
videoData = append(videoData, audioData...)
videoData = append(videoData, data1...)
//audio 2
videoData = append(videoData, 0xc4)
videoData = append(videoData, data1...)
//video 3
videoData = append(videoData, 0xc6)
videoData = append(videoData, data2...)
//audio 3
videoData = append(videoData, 0xc4)
videoData = append(videoData, data2...)
conn := &Conn{
pool: pool.NewPool(),
rw: NewReadWriter(bytes.NewBuffer(videoData), 1024),
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
chunks: make(map[uint32]ChunkStream),
}
var c ChunkStream
//video 1
err := conn.Read(&c)
at.Equal(err, nil)
at.Equal(int(c.TypeID), 9)
at.Equal(len(c.Data), 307)
//audio2
err = conn.Read(&c)
at.Equal(err, nil)
at.Equal(int(c.TypeID), 8)
at.Equal(len(c.Data), 307)
err = conn.Read(&c)
at.Equal(err, io.EOF)
}
func TestSetChunksizeForWrite(t *testing.T) {
at := assert.New(t)
chunk := ChunkStream{
Format: 0,
CSID: 2,
Timestamp: 0,
Length: 4,
StreamID: 1,
TypeID: idSetChunkSize,
Data: []byte{0x00, 0x00, 0x00, 0x96},
}
buf := bytes.NewBuffer(nil)
rw := NewReadWriter(buf, 1024)
conn := &Conn{
pool: pool.NewPool(),
rw: rw,
chunkSize: 128,
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
chunks: make(map[uint32]ChunkStream),
}
audio := ChunkStream{
Format: 0,
CSID: 4,
Timestamp: 40,
Length: 133,
StreamID: 1,
TypeID: 0x8,
}
audio.Data = make([]byte, 133)
audio.Data = audio.Data[:133]
audio.Data[0] = 0xff
audio.Data[128] = 0xff
err := conn.Write(&audio)
at.Equal(err, nil)
conn.Flush()
at.Equal(len(buf.Bytes()), 146)
buf.Reset()
err = conn.Write(&chunk)
at.Equal(err, nil)
conn.Flush()
buf.Reset()
err = conn.Write(&audio)
at.Equal(err, nil)
conn.Flush()
at.Equal(len(buf.Bytes()), 145)
}
func TestSetChunksize(t *testing.T) {
at := assert.New(t)
data := []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,
}
data1 := make([]byte, 128)
data2 := make([]byte, 51)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data2...)
rw := NewReadWriter(bytes.NewBuffer(data), 1024)
conn := &Conn{
pool: pool.NewPool(),
rw: rw,
chunkSize: 128,
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
chunks: make(map[uint32]ChunkStream),
}
var c ChunkStream
err := conn.Read(&c)
at.Equal(err, nil)
at.Equal(int(c.TypeID), 9)
at.Equal(int(c.CSID), 6)
at.Equal(int(c.StreamID), 1)
at.Equal(len(c.Data), 307)
//设置chunksize
chunkBuf := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x96}
conn.rw = NewReadWriter(bytes.NewBuffer(chunkBuf), 1024)
err = conn.Read(&c)
at.Equal(err, nil)
data = data[:12]
data[7] = 0x8
data1 = make([]byte, 150)
data2 = make([]byte, 7)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data1...)
data = append(data, 0xc6)
data = append(data, data2...)
conn.rw = NewReadWriter(bytes.NewBuffer(data), 1024)
err = conn.Read(&c)
at.Equal(err, nil)
at.Equal(len(c.Data), 307)
err = conn.Read(&c)
at.Equal(err, io.EOF)
}
func TestConnWrite(t *testing.T) {
at := assert.New(t)
wr := bytes.NewBuffer(nil)
readWriter := NewReadWriter(wr, 128)
conn := &Conn{
pool: pool.NewPool(),
rw: readWriter,
chunkSize: 128,
remoteChunkSize: 128,
windowAckSize: 2500000,
remoteWindowAckSize: 2500000,
chunks: make(map[uint32]ChunkStream),
}
c1 := ChunkStream{
Length: 3,
TypeID: 8,
CSID: 3,
Timestamp: 40,
Data: []byte{0x01, 0x02, 0x03},
}
err := conn.Write(&c1)
at.Equal(err, nil)
conn.Flush()
at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x28, 0x0, 0x0, 0x3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3})
//for type 1
wr.Reset()
c1 = ChunkStream{
Length: 4,
TypeID: 8,
CSID: 3,
Timestamp: 80,
Data: []byte{0x01, 0x02, 0x03, 0x4},
}
err = conn.Write(&c1)
at.Equal(err, nil)
conn.Flush()
at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x50, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4})
//for type 2
wr.Reset()
c1.Timestamp = 160
err = conn.Write(&c1)
at.Equal(err, nil)
conn.Flush()
at.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4})
}
================================================
FILE: protocol/rtmp/core/handshake.go
================================================
package core
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"time"
"github.com/gwuhaolin/livego/utils/pio"
)
var (
timeout = 5 * time.Second
)
var (
hsClientFullKey = []byte{
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,
}
hsServerFullKey = []byte{
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
'S', 'e', 'r', 'v', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,
}
hsClientPartialKey = hsClientFullKey[:30]
hsServerPartialKey = hsServerFullKey[:36]
)
func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) {
h := hmac.New(sha256.New, key)
if gap <= 0 {
h.Write(src)
} else {
h.Write(src[:gap])
h.Write(src[gap+32:])
}
return h.Sum(nil)
}
func hsCalcDigestPos(p []byte, base int) (pos int) {
for i := 0; i < 4; i++ {
pos += int(p[base+i])
}
pos = (pos % 728) + base + 4
return
}
func hsFindDigest(p []byte, key []byte, base int) int {
gap := hsCalcDigestPos(p, base)
digest := hsMakeDigest(key, p, gap)
if bytes.Compare(p[gap:gap+32], digest) != 0 {
return -1
}
return gap
}
func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []byte) {
var pos int
if pos = hsFindDigest(p, peerkey, 772); pos == -1 {
if pos = hsFindDigest(p, peerkey, 8); pos == -1 {
return
}
}
ok = true
digest = hsMakeDigest(key, p[pos:pos+32], -1)
return
}
func hsCreate01(p []byte, time uint32, ver uint32, key []byte) {
p[0] = 3
p1 := p[1:]
rand.Read(p1[8:])
pio.PutU32BE(p1[0:4], time)
pio.PutU32BE(p1[4:8], ver)
gap := hsCalcDigestPos(p1, 8)
digest := hsMakeDigest(key, p1, gap)
copy(p1[gap:], digest)
}
func hsCreate2(p []byte, key []byte) {
rand.Read(p)
gap := len(p) - 32
digest := hsMakeDigest(key, p, gap)
copy(p[gap:], digest)
}
func (conn *Conn) HandshakeClient() (err error) {
var random [(1 + 1536*2) * 2]byte
C0C1C2 := random[:1536*2+1]
C0 := C0C1C2[:1]
C0C1 := C0C1C2[:1536+1]
C2 := C0C1C2[1536+1:]
S0S1S2 := random[1536*2+1:]
C0[0] = 3
// > C0C1
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = conn.rw.Write(C0C1); err != nil {
return
}
conn.Conn.SetDeadline(time.Now().Add(timeout))
if err = conn.rw.Flush(); err != nil {
return
}
// < S0S1S2
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = io.ReadFull(conn.rw, S0S1S2); err != nil {
return
}
S1 := S0S1S2[1 : 1536+1]
if ver := pio.U32BE(S1[4:8]); ver != 0 {
C2 = S1
} else {
C2 = S1
}
// > C2
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = conn.rw.Write(C2); err != nil {
return
}
conn.Conn.SetDeadline(time.Time{})
return
}
func (conn *Conn) HandshakeServer() (err error) {
var random [(1 + 1536*2) * 2]byte
C0C1C2 := random[:1536*2+1]
C0 := C0C1C2[:1]
C1 := C0C1C2[1 : 1536+1]
C0C1 := C0C1C2[:1536+1]
C2 := C0C1C2[1536+1:]
S0S1S2 := random[1536*2+1:]
S0 := S0S1S2[:1]
S1 := S0S1S2[1 : 1536+1]
S0S1 := S0S1S2[:1536+1]
S2 := S0S1S2[1536+1:]
// < C0C1
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = io.ReadFull(conn.rw, C0C1); err != nil {
return
}
conn.Conn.SetDeadline(time.Now().Add(timeout))
if C0[0] != 3 {
err = fmt.Errorf("rtmp: handshake version=%d invalid", C0[0])
return
}
S0[0] = 3
clitime := pio.U32BE(C1[0:4])
srvtime := clitime
srvver := uint32(0x0d0e0a0d)
cliver := pio.U32BE(C1[4:8])
if cliver != 0 {
var ok bool
var digest []byte
if ok, digest = hsParse1(C1, hsClientPartialKey, hsServerFullKey); !ok {
err = fmt.Errorf("rtmp: handshake server: C1 invalid")
return
}
hsCreate01(S0S1, srvtime, srvver, hsServerPartialKey)
hsCreate2(S2, digest)
} else {
copy(S1, C2)
copy(S2, C1)
}
// > S0S1S2
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = conn.rw.Write(S0S1S2); err != nil {
return
}
conn.Conn.SetDeadline(time.Now().Add(timeout))
if err = conn.rw.Flush(); err != nil {
return
}
// < C2
conn.Conn.SetDeadline(time.Now().Add(timeout))
if _, err = io.ReadFull(conn.rw, C2); err != nil {
return
}
conn.Conn.SetDeadline(time.Time{})
return
}
================================================
FILE: protocol/rtmp/core/read_writer.go
================================================
package core
import (
"bufio"
"io"
)
type ReadWriter struct {
*bufio.ReadWriter
readError error
writeError error
}
func NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter {
return &ReadWriter{
ReadWriter: bufio.NewReadWriter(bufio.NewReaderSize(rw, bufSize), bufio.NewWriterSize(rw, bufSize)),
}
}
func (rw *ReadWriter) Read(p []byte) (int, error) {
if rw.readError != nil {
return 0, rw.readError
}
n, err := io.ReadAtLeast(rw.ReadWriter, p, len(p))
rw.readError = err
return n, err
}
func (rw *ReadWriter) ReadError() error {
return rw.readError
}
func (rw *ReadWriter) ReadUintBE(n int) (uint32, error) {
if rw.readError != nil {
return 0, rw.readError
}
ret := uint32(0)
for i := 0; i < n; i++ {
b, err := rw.ReadByte()
if err != nil {
rw.readError = err
return 0, err
}
ret = ret<<8 + uint32(b)
}
return ret, nil
}
func (rw *ReadWriter) ReadUintLE(n int) (uint32, error) {
if rw.readError != nil {
return 0, rw.readError
}
ret := uint32(0)
for i := 0; i < n; i++ {
b, err := rw.ReadByte()
if err != nil {
rw.readError = err
return 0, err
}
ret += uint32(b) << uint32(i*8)
}
return ret, nil
}
func (rw *ReadWriter) Flush() error {
if rw.writeError != nil {
return rw.writeError
}
if rw.ReadWriter.Writer.Buffered() == 0 {
return nil
}
return rw.ReadWriter.Flush()
}
func (rw *ReadWriter) Write(p []byte) (int, error) {
if rw.writeError != nil {
return 0, rw.writeError
}
return rw.ReadWriter.Write(p)
}
func (rw *ReadWriter) WriteError() error {
return rw.writeError
}
func (rw *ReadWriter) WriteUintBE(v uint32, n int) error {
if rw.writeError != nil {
return rw.writeError
}
for i := 0; i < n; i++ {
b := byte(v>>uint32((n-i-1)<<3)) & 0xff
if err := rw.WriteByte(b); err != nil {
rw.writeError = err
return err
}
}
return nil
}
func (rw *ReadWriter) WriteUintLE(v uint32, n int) error {
if rw.writeError != nil {
return rw.writeError
}
for i := 0; i < n; i++ {
b := byte(v) & 0xff
if err := rw.WriteByte(b); err != nil {
rw.writeError = err
return err
}
v = v >> 8
}
return nil
}
================================================
FILE: protocol/rtmp/core/read_writer_test.go
================================================
package core
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestReader(t *testing.T) {
at := assert.New(t)
buf := bytes.NewBufferString("abc")
r := NewReadWriter(buf, 1024)
b := make([]byte, 3)
n, err := r.Read(b)
at.Equal(err, nil)
at.Equal(r.ReadError(), nil)
at.Equal(n, 3)
n, err = r.Read(b)
at.Equal(err, io.EOF)
at.Equal(r.ReadError(), io.EOF)
buf.WriteString("123")
n, err = r.Read(b)
at.Equal(err, io.EOF)
at.Equal(r.ReadError(), io.EOF)
at.Equal(n, 0)
}
func TestReaderUintBE(t *testing.T) {
at := assert.New(t)
type Test struct {
i int
value uint32
bytes []byte
}
tests := []Test{
{1, 0x01, []byte{0x01}},
{2, 0x0102, []byte{0x01, 0x02}},
{3, 0x010203, []byte{0x01, 0x02, 0x03}},
{4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}},
}
for _, test := range tests {
buf := bytes.NewBuffer(test.bytes)
r := NewReadWriter(buf, 1024)
n, err := r.ReadUintBE(test.i)
at.Equal(err, nil, "test %d", test.i)
at.Equal(n, test.value, "test %d", test.i)
}
}
func TestReaderUintLE(t *testing.T) {
at := assert.New(t)
type Test struct {
i int
value uint32
bytes []byte
}
tests := []Test{
{1, 0x01, []byte{0x01}},
{2, 0x0102, []byte{0x02, 0x01}},
{3, 0x010203, []byte{0x03, 0x02, 0x01}},
{4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}},
}
for _, test := range tests {
buf := bytes.NewBuffer(test.bytes)
r := NewReadWriter(buf, 1024)
n, err := r.ReadUintLE(test.i)
at.Equal(err, nil, "test %d", test.i)
at.Equal(n, test.value, "test %d", test.i)
}
}
func TestWriter(t *testing.T) {
at := assert.New(t)
buf := bytes.NewBuffer(nil)
w := NewReadWriter(buf, 1024)
b := []byte{1, 2, 3}
n, err := w.Write(b)
at.Equal(err, nil)
at.Equal(w.WriteError(), nil)
at.Equal(n, 3)
w.writeError = io.EOF
n, err = w.Write(b)
at.Equal(err, io.EOF)
at.Equal(w.WriteError(), io.EOF)
at.Equal(n, 0)
}
func TestWriteUintBE(t *testing.T) {
at := assert.New(t)
type Test struct {
i int
value uint32
bytes []byte
}
tests := []Test{
{1, 0x01, []byte{0x01}},
{2, 0x0102, []byte{0x01, 0x02}},
{3, 0x010203, []byte{0x01, 0x02, 0x03}},
{4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
r := NewReadWriter(buf, 1024)
err := r.WriteUintBE(test.value, test.i)
at.Equal(err, nil, "test %d", test.i)
err = r.Flush()
at.Equal(err, nil, "test %d", test.i)
at.Equal(buf.Bytes(), test.bytes, "test %d", test.i)
}
}
func TestWriteUintLE(t *testing.T) {
at := assert.New(t)
type Test struct {
i int
value uint32
bytes []byte
}
tests := []Test{
{1, 0x01, []byte{0x01}},
{2, 0x0102, []byte{0x02, 0x01}},
{3, 0x010203, []byte{0x03, 0x02, 0x01}},
{4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
r := NewReadWriter(buf, 1024)
err := r.WriteUintLE(test.value, test.i)
at.Equal(err, nil, "test %d", test.i)
err = r.Flush()
at.Equal(err, nil, "test %d", test.i)
at.Equal(buf.Bytes(), test.bytes, "test %d", test.i)
}
}
================================================
FILE: protocol/rtmp/rtmp.go
================================================
package rtmp
import (
"fmt"
"net"
"net/url"
"reflect"
"strings"
"time"
"github.com/gwuhaolin/livego/utils/uid"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/container/flv"
"github.com/gwuhaolin/livego/protocol/rtmp/core"
log "github.com/sirupsen/logrus"
)
const (
maxQueueNum = 1024
SAVE_STATICS_INTERVAL = 5000
)
var (
readTimeout = configure.Config.GetInt("read_timeout")
writeTimeout = configure.Config.GetInt("write_timeout")
)
type Client struct {
handler av.Handler
getter av.GetWriter
}
func NewRtmpClient(h av.Handler, getter av.GetWriter) *Client {
return &Client{
handler: h,
getter: getter,
}
}
func (c *Client) Dial(url string, method string) error {
connClient := core.NewConnClient()
if err := connClient.Start(url, method); err != nil {
return err
}
if method == av.PUBLISH {
writer := NewVirWriter(connClient)
log.Debugf("client Dial call NewVirWriter url=%s, method=%s", url, method)
c.handler.HandleWriter(writer)
} else if method == av.PLAY {
reader := NewVirReader(connClient)
log.Debugf("client Dial call NewVirReader url=%s, method=%s", url, method)
c.handler.HandleReader(reader)
if c.getter != nil {
writer := c.getter.GetWriter(reader.Info())
c.handler.HandleWriter(writer)
}
}
return nil
}
func (c *Client) GetHandle() av.Handler {
return c.handler
}
type Server struct {
handler av.Handler
getter av.GetWriter
}
func NewRtmpServer(h av.Handler, getter av.GetWriter) *Server {
return &Server{
handler: h,
getter: getter,
}
}
func (s *Server) Serve(listener net.Listener) (err error) {
defer func() {
if r := recover(); r != nil {
log.Error("rtmp serve panic: ", r)
}
}()
for {
var netconn net.Conn
netconn, err = listener.Accept()
if err != nil {
return
}
conn := core.NewConn(netconn, 4*1024)
log.Debug("new client, connect remote: ", conn.RemoteAddr().String(),
"local:", conn.LocalAddr().String())
go s.handleConn(conn)
}
}
func (s *Server) handleConn(conn *core.Conn) error {
if err := conn.HandshakeServer(); err != nil {
conn.Close()
log.Error("handleConn HandshakeServer err: ", err)
return err
}
connServer := core.NewConnServer(conn)
if err := connServer.ReadMsg(); err != nil {
conn.Close()
log.Error("handleConn read msg err: ", err)
return err
}
appname, name, _ := connServer.GetInfo()
if ret := configure.CheckAppName(appname); !ret {
err := fmt.Errorf("application name=%s is not configured", appname)
conn.Close()
log.Error("CheckAppName err: ", err)
return err
}
log.Debugf("handleConn: IsPublisher=%v", connServer.IsPublisher())
if connServer.IsPublisher() {
if configure.Config.GetBool("rtmp_noauth") {
key, err := configure.RoomKeys.GetKey(name)
if err != nil {
err := fmt.Errorf("Cannot create key err=%s", err.Error())
conn.Close()
log.Error("GetKey err: ", err)
return err
}
name = key
}
channel, err := configure.RoomKeys.GetChannel(name)
if err != nil {
err := fmt.Errorf("invalid key err=%s", err.Error())
conn.Close()
log.Error("CheckKey err: ", err)
return err
}
connServer.PublishInfo.Name = channel
if pushlist, ret := configure.GetStaticPushUrlList(appname); ret && (pushlist != nil) {
log.Debugf("GetStaticPushUrlList: %v", pushlist)
}
reader := NewVirReader(connServer)
s.handler.HandleReader(reader)
log.Debugf("new publisher: %+v", reader.Info())
if s.getter != nil {
writeType := reflect.TypeOf(s.getter)
log.Debugf("handleConn:writeType=%v", writeType)
writer := s.getter.GetWriter(reader.Info())
s.handler.HandleWriter(writer)
}
if configure.Config.GetBool("flv_archive") {
flvWriter := new(flv.FlvDvr)
s.handler.HandleWriter(flvWriter.GetWriter(reader.Info()))
}
} else {
writer := NewVirWriter(connServer)
log.Debugf("new player: %+v", writer.Info())
s.handler.HandleWriter(writer)
}
return nil
}
type GetInFo interface {
GetInfo() (string, string, string)
}
type StreamReadWriteCloser interface {
GetInFo
Close(error)
Write(core.ChunkStream) error
Read(c *core.ChunkStream) error
}
type StaticsBW struct {
StreamId uint32
VideoDatainBytes uint64
LastVideoDatainBytes uint64
VideoSpeedInBytesperMS uint64
AudioDatainBytes uint64
LastAudioDatainBytes uint64
AudioSpeedInBytesperMS uint64
LastTimestamp int64
}
type VirWriter struct {
Uid string
closed bool
av.RWBaser
conn StreamReadWriteCloser
packetQueue chan *av.Packet
WriteBWInfo StaticsBW
}
func NewVirWriter(conn StreamReadWriteCloser) *VirWriter {
ret := &VirWriter{
Uid: uid.NewId(),
conn: conn,
RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)),
packetQueue: make(chan *av.Packet, maxQueueNum),
WriteBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},
}
go ret.Check()
go func() {
err := ret.SendPacket()
if err != nil {
log.Warning(err)
}
}()
return ret
}
func (v *VirWriter) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) {
nowInMS := int64(time.Now().UnixNano() / 1e6)
v.WriteBWInfo.StreamId = streamid
if isVideoFlag {
v.WriteBWInfo.VideoDatainBytes = v.WriteBWInfo.VideoDatainBytes + length
} else {
v.WriteBWInfo.AudioDatainBytes = v.WriteBWInfo.AudioDatainBytes + length
}
if v.WriteBWInfo.LastTimestamp == 0 {
v.WriteBWInfo.LastTimestamp = nowInMS
} else if (nowInMS - v.WriteBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL {
diffTimestamp := (nowInMS - v.WriteBWInfo.LastTimestamp) / 1000
v.WriteBWInfo.VideoSpeedInBytesperMS = (v.WriteBWInfo.VideoDatainBytes - v.WriteBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000
v.WriteBWInfo.AudioSpeedInBytesperMS = (v.WriteBWInfo.AudioDatainBytes - v.WriteBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000
v.WriteBWInfo.LastVideoDatainBytes = v.WriteBWInfo.VideoDatainBytes
v.WriteBWInfo.LastAudioDatainBytes = v.WriteBWInfo.AudioDatainBytes
v.WriteBWInfo.LastTimestamp = nowInMS
}
}
func (v *VirWriter) Check() {
var c core.ChunkStream
for {
if err := v.conn.Read(&c); err != nil {
v.Close(err)
return
}
}
}
func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue
// try to don't drop audio
if ok && tmpPkt.IsAudio {
if len(pktQue) > maxQueueNum-2 {
log.Debug("drop audio pkt")
<-pktQue
} else {
pktQue <- tmpPkt
}
}
if ok && tmpPkt.IsVideo {
videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)
// dont't drop sps config and dont't drop key frame
if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {
pktQue <- tmpPkt
}
if len(pktQue) > maxQueueNum-10 {
log.Debug("drop video pkt")
<-pktQue
}
}
}
log.Debug("packet queue len: ", len(pktQue))
}
//
func (v *VirWriter) Write(p *av.Packet) (err error) {
err = nil
if v.closed {
err = fmt.Errorf("VirWriter closed")
return
}
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("VirWriter has already been closed:%v", e)
}
}()
if len(v.packetQueue) >= maxQueueNum-24 {
v.DropPacket(v.packetQueue, v.Info())
} else {
v.packetQueue <- p
}
return
}
func (v *VirWriter) SendPacket() error {
Flush := reflect.ValueOf(v.conn).MethodByName("Flush")
var cs core.ChunkStream
for {
p, ok := <-v.packetQueue
if ok {
cs.Data = p.Data
cs.Length = uint32(len(p.Data))
cs.StreamID = p.StreamID
cs.Timestamp = p.TimeStamp
cs.Timestamp += v.BaseTimeStamp()
if p.IsVideo {
cs.TypeID = av.TAG_VIDEO
} else {
if p.IsMetadata {
cs.TypeID = av.TAG_SCRIPTDATAAMF0
} else {
cs.TypeID = av.TAG_AUDIO
}
}
v.SaveStatics(p.StreamID, uint64(cs.Length), p.IsVideo)
v.SetPreTime()
v.RecTimeStamp(cs.Timestamp, cs.TypeID)
err := v.conn.Write(cs)
if err != nil {
v.closed = true
return err
}
Flush.Call(nil)
} else {
return fmt.Errorf("closed")
}
}
}
func (v *VirWriter) Info() (ret av.Info) {
ret.UID = v.Uid
_, _, URL := v.conn.GetInfo()
ret.URL = URL
_url, err := url.Parse(URL)
if err != nil {
log.Warning(err)
}
ret.Key = strings.TrimLeft(_url.Path, "/")
ret.Inter = true
return
}
func (v *VirWriter) Close(err error) {
log.Warning("player ", v.Info(), "closed: "+err.Error())
if !v.closed {
close(v.packetQueue)
}
v.closed = true
v.conn.Close(err)
}
type VirReader struct {
Uid string
av.RWBaser
demuxer *flv.Demuxer
conn StreamReadWriteCloser
ReadBWInfo StaticsBW
}
func NewVirReader(conn StreamReadWriteCloser) *VirReader {
return &VirReader{
Uid: uid.NewId(),
conn: conn,
RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)),
demuxer: flv.NewDemuxer(),
ReadBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},
}
}
func (v *VirReader) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) {
nowInMS := int64(time.Now().UnixNano() / 1e6)
v.ReadBWInfo.StreamId = streamid
if isVideoFlag {
v.ReadBWInfo.VideoDatainBytes = v.ReadBWInfo.VideoDatainBytes + length
} else {
v.ReadBWInfo.AudioDatainBytes = v.ReadBWInfo.AudioDatainBytes + length
}
if v.ReadBWInfo.LastTimestamp == 0 {
v.ReadBWInfo.LastTimestamp = nowInMS
} else if (nowInMS - v.ReadBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL {
diffTimestamp := (nowInMS - v.ReadBWInfo.LastTimestamp) / 1000
//log.Printf("now=%d, last=%d, diff=%d", nowInMS, v.ReadBWInfo.LastTimestamp, diffTimestamp)
v.ReadBWInfo.VideoSpeedInBytesperMS = (v.ReadBWInfo.VideoDatainBytes - v.ReadBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000
v.ReadBWInfo.AudioSpeedInBytesperMS = (v.ReadBWInfo.AudioDatainBytes - v.ReadBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000
v.ReadBWInfo.LastVideoDatainBytes = v.ReadBWInfo.VideoDatainBytes
v.ReadBWInfo.LastAudioDatainBytes = v.ReadBWInfo.AudioDatainBytes
v.ReadBWInfo.LastTimestamp = nowInMS
}
}
func (v *VirReader) Read(p *av.Packet) (err error) {
defer func() {
if r := recover(); r != nil {
log.Warning("rtmp read packet panic: ", r)
}
}()
v.SetPreTime()
var cs core.ChunkStream
for {
err = v.conn.Read(&cs)
if err != nil {
return err
}
if cs.TypeID == av.TAG_AUDIO ||
cs.TypeID == av.TAG_VIDEO ||
cs.TypeID == av.TAG_SCRIPTDATAAMF0 ||
cs.TypeID == av.TAG_SCRIPTDATAAMF3 {
break
}
}
p.IsAudio = cs.TypeID == av.TAG_AUDIO
p.IsVideo = cs.TypeID == av.TAG_VIDEO
p.IsMetadata = cs.TypeID == av.TAG_SCRIPTDATAAMF0 || cs.TypeID == av.TAG_SCRIPTDATAAMF3
p.StreamID = cs.StreamID
p.Data = cs.Data
p.TimeStamp = cs.Timestamp
v.SaveStatics(p.StreamID, uint64(len(p.Data)), p.IsVideo)
v.demuxer.DemuxH(p)
return err
}
func (v *VirReader) Info() (ret av.Info) {
ret.UID = v.Uid
_, _, URL := v.conn.GetInfo()
ret.URL = URL
_url, err := url.Parse(URL)
if err != nil {
log.Warning(err)
}
ret.Key = strings.TrimLeft(_url.Path, "/")
return
}
func (v *VirReader) Close(err error) {
log.Debug("publisher ", v.Info(), "closed: "+err.Error())
v.conn.Close(err)
}
================================================
FILE: protocol/rtmp/rtmprelay/rtmprelay.go
================================================
package rtmprelay
import (
"bytes"
"fmt"
"io"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/protocol/rtmp/core"
log "github.com/sirupsen/logrus"
)
var (
STOP_CTRL = "RTMPRELAY_STOP"
)
type RtmpRelay struct {
PlayUrl string
PublishUrl string
cs_chan chan core.ChunkStream
sndctrl_chan chan string
connectPlayClient *core.ConnClient
connectPublishClient *core.ConnClient
startflag bool
}
func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay {
return &RtmpRelay{
PlayUrl: *playurl,
PublishUrl: *publishurl,
cs_chan: make(chan core.ChunkStream, 500),
sndctrl_chan: make(chan string),
connectPlayClient: nil,
connectPublishClient: nil,
startflag: false,
}
}
func (self *RtmpRelay) rcvPlayChunkStream() {
log.Debug("rcvPlayRtmpMediaPacket connectClient.Read...")
for {
var rc core.ChunkStream
if self.startflag == false {
self.connectPlayClient.Close(nil)
log.Debugf("rcvPlayChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
break
}
err := self.connectPlayClient.Read(&rc)
if err != nil && err == io.EOF {
break
}
//log.Debugf("connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v", rc.TypeID, len(rc.Data), err)
switch rc.TypeID {
case 20, 17:
r := bytes.NewReader(rc.Data)
vs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0)
log.Debugf("rcvPlayRtmpMediaPacket: vs=%v, err=%v", vs, err)
case 18:
log.Debug("rcvPlayRtmpMediaPacket: metadata....")
self.cs_chan <- rc
case 8, 9:
self.cs_chan <- rc
}
}
}
func (self *RtmpRelay) sendPublishChunkStream() {
for {
select {
case rc := <-self.cs_chan:
//log.Debugf("sendPublishChunkStream: rc.TypeID=%v length=%d", rc.TypeID, len(rc.Data))
self.connectPublishClient.Write(rc)
case ctrlcmd := <-self.sndctrl_chan:
if ctrlcmd == STOP_CTRL {
self.connectPublishClient.Close(nil)
log.Debugf("sendPublishChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
return
}
}
}
}
func (self *RtmpRelay) Start() error {
if self.startflag {
return fmt.Errorf("The rtmprelay already started, playurl=%s, publishurl=%s\n", self.PlayUrl, self.PublishUrl)
}
self.connectPlayClient = core.NewConnClient()
self.connectPublishClient = core.NewConnClient()
log.Debugf("play server addr:%v starting....", self.PlayUrl)
err := self.connectPlayClient.Start(self.PlayUrl, av.PLAY)
if err != nil {
log.Debugf("connectPlayClient.Start url=%v error", self.PlayUrl)
return err
}
log.Debugf("publish server addr:%v starting....", self.PublishUrl)
err = self.connectPublishClient.Start(self.PublishUrl, av.PUBLISH)
if err != nil {
log.Debugf("connectPublishClient.Start url=%v error", self.PublishUrl)
self.connectPlayClient.Close(nil)
return err
}
self.startflag = true
go self.rcvPlayChunkStream()
go self.sendPublishChunkStream()
return nil
}
func (self *RtmpRelay) Stop() {
if !self.startflag {
log.Debugf("The rtmprelay already stoped, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
return
}
self.startflag = false
self.sndctrl_chan <- STOP_CTRL
}
================================================
FILE: protocol/rtmp/rtmprelay/staticrelay.go
================================================
package rtmprelay
import (
"fmt"
"sync"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/rtmp/core"
log "github.com/sirupsen/logrus"
)
type StaticPush struct {
RtmpUrl string
packet_chan chan *av.Packet
sndctrl_chan chan string
connectClient *core.ConnClient
startflag bool
}
var G_StaticPushMap = make(map[string](*StaticPush))
var g_MapLock = new(sync.RWMutex)
var G_PushUrlList []string = nil
var (
STATIC_RELAY_STOP_CTRL = "STATIC_RTMPRELAY_STOP"
)
func GetStaticPushList(appname string) ([]string, error) {
if G_PushUrlList == nil {
// Do not unmarshel the config every time, lots of reflect works -gs
pushurlList, ok := configure.GetStaticPushUrlList(appname)
if !ok {
G_PushUrlList = []string{}
} else {
G_PushUrlList = pushurlList
}
}
if len(G_PushUrlList) == 0 {
return nil, fmt.Errorf("no static push url")
}
return G_PushUrlList, nil
}
func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush {
g_MapLock.RLock()
staticpush, ok := G_StaticPushMap[rtmpurl]
log.Debugf("GetAndCreateStaticPushObject: %s, return %v", rtmpurl, ok)
if !ok {
g_MapLock.RUnlock()
newStaticpush := NewStaticPush(rtmpurl)
g_MapLock.Lock()
G_StaticPushMap[rtmpurl] = newStaticpush
g_MapLock.Unlock()
return newStaticpush
}
g_MapLock.RUnlock()
return staticpush
}
func GetStaticPushObject(rtmpurl string) (*StaticPush, error) {
g_MapLock.RLock()
if staticpush, ok := G_StaticPushMap[rtmpurl]; ok {
g_MapLock.RUnlock()
return staticpush, nil
}
g_MapLock.RUnlock()
return nil, fmt.Errorf("G_StaticPushMap[%s] not exist....", rtmpurl)
}
func ReleaseStaticPushObject(rtmpurl string) {
g_MapLock.RLock()
if _, ok := G_StaticPushMap[rtmpurl]; ok {
g_MapLock.RUnlock()
log.Debugf("ReleaseStaticPushObject %s ok", rtmpurl)
g_MapLock.Lock()
delete(G_StaticPushMap, rtmpurl)
g_MapLock.Unlock()
} else {
g_MapLock.RUnlock()
log.Debugf("ReleaseStaticPushObject: not find %s", rtmpurl)
}
}
func NewStaticPush(rtmpurl string) *StaticPush {
return &StaticPush{
RtmpUrl: rtmpurl,
packet_chan: make(chan *av.Packet, 500),
sndctrl_chan: make(chan string),
connectClient: nil,
startflag: false,
}
}
func (self *StaticPush) Start() error {
if self.startflag {
return fmt.Errorf("StaticPush already start %s", self.RtmpUrl)
}
self.connectClient = core.NewConnClient()
log.Debugf("static publish server addr:%v starting....", self.RtmpUrl)
err := self.connectClient.Start(self.RtmpUrl, "publish")
if err != nil {
log.Debugf("connectClient.Start url=%v error", self.RtmpUrl)
return err
}
log.Debugf("static publish server addr:%v started, streamid=%d", self.RtmpUrl, self.connectClient.GetStreamId())
go self.HandleAvPacket()
self.startflag = true
return nil
}
func (self *StaticPush) Stop() {
if !self.startflag {
return
}
log.Debugf("StaticPush Stop: %s", self.RtmpUrl)
self.sndctrl_chan <- STATIC_RELAY_STOP_CTRL
self.startflag = false
}
func (self *StaticPush) WriteAvPacket(packet *av.Packet) {
if !self.startflag {
return
}
self.packet_chan <- packet
}
func (self *StaticPush) sendPacket(p *av.Packet) {
if !self.startflag {
return
}
var cs core.ChunkStream
cs.Data = p.Data
cs.Length = uint32(len(p.Data))
cs.StreamID = self.connectClient.GetStreamId()
cs.Timestamp = p.TimeStamp
//cs.Timestamp += v.BaseTimeStamp()
//log.Printf("Static sendPacket: rtmpurl=%s, length=%d, streamid=%d",
// self.RtmpUrl, len(p.Data), cs.StreamID)
if p.IsVideo {
cs.TypeID = av.TAG_VIDEO
} else {
if p.IsMetadata {
cs.TypeID = av.TAG_SCRIPTDATAAMF0
} else {
cs.TypeID = av.TAG_AUDIO
}
}
self.connectClient.Write(cs)
}
func (self *StaticPush) HandleAvPacket() {
if !self.IsStart() {
log.Debugf("static push %s not started", self.RtmpUrl)
return
}
for {
select {
case packet := <-self.packet_chan:
self.sendPacket(packet)
case ctrlcmd := <-self.sndctrl_chan:
if ctrlcmd == STATIC_RELAY_STOP_CTRL {
self.connectClient.Close(nil)
log.Debugf("Static HandleAvPacket close: publishurl=%s", self.RtmpUrl)
return
}
}
}
}
func (self *StaticPush) IsStart() bool {
return self.startflag
}
================================================
FILE: protocol/rtmp/stream.go
================================================
package rtmp
import (
"fmt"
"strings"
"sync"
"time"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/rtmp/cache"
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay"
log "github.com/sirupsen/logrus"
)
var (
EmptyID = ""
)
type RtmpStream struct {
streams *sync.Map //key
}
func NewRtmpStream() *RtmpStream {
ret := &RtmpStream{
streams: &sync.Map{},
}
go ret.CheckAlive()
return ret
}
func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
info := r.Info()
log.Debugf("HandleReader: info[%v]", info)
var stream *Stream
i, ok := rs.streams.Load(info.Key)
if stream, ok = i.(*Stream); ok {
stream.TransStop()
id := stream.ID()
if id != EmptyID && id != info.UID {
ns := NewStream()
stream.Copy(ns)
stream = ns
rs.streams.Store(info.Key, ns)
}
} else {
stream = NewStream()
rs.streams.Store(info.Key, stream)
stream.info = info
}
stream.AddReader(r)
}
func (rs *RtmpStream) HandleWriter(w av.WriteCloser) {
info := w.Info()
log.Debugf("HandleWriter: info[%v]", info)
var s *Stream
item, ok := rs.streams.Load(info.Key)
if !ok {
log.Debugf("HandleWriter: not found create new info[%v]", info)
s = NewStream()
rs.streams.Store(info.Key, s)
s.info = info
} else {
s = item.(*Stream)
s.AddWriter(w)
}
}
func (rs *RtmpStream) GetStreams() *sync.Map {
return rs.streams
}
func (rs *RtmpStream) CheckAlive() {
for {
<-time.After(5 * time.Second)
rs.streams.Range(func(key, val interface{}) bool {
v := val.(*Stream)
if v.CheckAlive() == 0 {
rs.streams.Delete(key)
}
return true
})
}
}
type Stream struct {
isStart bool
cache *cache.Cache
r av.ReadCloser
ws *sync.Map
info av.Info
}
type PackWriterCloser struct {
init bool
w av.WriteCloser
}
func (p *PackWriterCloser) GetWriter() av.WriteCloser {
return p.w
}
func NewStream() *Stream {
return &Stream{
cache: cache.NewCache(),
ws: &sync.Map{},
}
}
func (s *Stream) ID() string {
if s.r != nil {
return s.r.Info().UID
}
return EmptyID
}
func (s *Stream) GetReader() av.ReadCloser {
return s.r
}
func (s *Stream) GetWs() *sync.Map {
return s.ws
}
func (s *Stream) Copy(dst *Stream) {
dst.info = s.info
s.ws.Range(func(key, val interface{}) bool {
v := val.(*PackWriterCloser)
s.ws.Delete(key)
v.w.CalcBaseTimestamp()
dst.AddWriter(v.w)
return true
})
}
func (s *Stream) AddReader(r av.ReadCloser) {
s.r = r
go s.TransStart()
}
func (s *Stream) AddWriter(w av.WriteCloser) {
info := w.Info()
pw := &PackWriterCloser{w: w}
s.ws.Store(info.UID, pw)
}
/*检测本application下是否配置static_push,
如果配置, 启动push远端的连接*/
func (s *Stream) StartStaticPush() {
key := s.info.Key
dscr := strings.Split(key, "/")
if len(dscr) < 1 {
return
}
index := strings.Index(key, "/")
if index < 0 {
return
}
streamname := key[index+1:]
appname := dscr[0]
log.Debugf("StartStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 {
log.Debugf("StartStaticPush: GetStaticPushList error=%v", err)
return
}
for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname
log.Debugf("StartStaticPush: static pushurl=%s", pushurl)
staticpushObj := rtmprelay.GetAndCreateStaticPushObject(pushurl)
if staticpushObj != nil {
if err := staticpushObj.Start(); err != nil {
log.Debugf("StartStaticPush: staticpushObj.Start %s error=%v", pushurl, err)
} else {
log.Debugf("StartStaticPush: staticpushObj.Start %s ok", pushurl)
}
} else {
log.Debugf("StartStaticPush GetStaticPushObject %s error", pushurl)
}
}
}
func (s *Stream) StopStaticPush() {
key := s.info.Key
log.Debugf("StopStaticPush......%s", key)
dscr := strings.Split(key, "/")
if len(dscr) < 1 {
return
}
index := strings.Index(key, "/")
if index < 0 {
return
}
streamname := key[index+1:]
appname := dscr[0]
log.Debugf("StopStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 {
log.Debugf("StopStaticPush: GetStaticPushList error=%v", err)
return
}
for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname
log.Debugf("StopStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) {
staticpushObj.Stop()
rtmprelay.ReleaseStaticPushObject(pushurl)
log.Debugf("StopStaticPush: staticpushObj.Stop %s ", pushurl)
} else {
log.Debugf("StopStaticPush GetStaticPushObject %s error", pushurl)
}
}
}
func (s *Stream) IsSendStaticPush() bool {
key := s.info.Key
dscr := strings.Split(key, "/")
if len(dscr) < 1 {
return false
}
appname := dscr[0]
//log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 {
//log.Debugf("SendStaticPush: GetStaticPushList error=%v", err)
return false
}
index := strings.Index(key, "/")
if index < 0 {
return false
}
streamname := key[index+1:]
for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname
//log.Debugf("SendStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) {
return true
//staticpushObj.WriteAvPacket(&packet)
//log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl)
} else {
log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl)
}
}
return false
}
func (s *Stream) SendStaticPush(packet av.Packet) {
key := s.info.Key
dscr := strings.Split(key, "/")
if len(dscr) < 1 {
return
}
index := strings.Index(key, "/")
if index < 0 {
return
}
streamname := key[index+1:]
appname := dscr[0]
//log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 {
//log.Debugf("SendStaticPush: GetStaticPushList error=%v", err)
return
}
for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname
//log.Debugf("SendStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) {
staticpushObj.WriteAvPacket(&packet)
//log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl)
} else {
log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl)
}
}
}
func (s *Stream) TransStart() {
s.isStart = true
var p av.Packet
log.Debugf("TransStart: %v", s.info)
s.StartStaticPush()
for {
if !s.isStart {
s.closeInter()
return
}
err := s.r.Read(&p)
if err != nil {
s.closeInter()
s.isStart = false
return
}
if s.IsSendStaticPush() {
s.SendStaticPush(p)
}
s.cache.Write(p)
s.ws.Range(func(key, val interface{}) bool {
v := val.(*PackWriterCloser)
if !v.init {
//log.Debugf("cache.send: %v", v.w.Info())
if err = s.cache.Send(v.w); err != nil {
log.Debugf("[%s] send cache packet error: %v, remove", v.w.Info(), err)
s.ws.Delete(key)
return true
}
v.init = true
} else {
newPacket := p
//writeType := reflect.TypeOf(v.w)
//log.Debugf("w.Write: type=%v, %v", writeType, v.w.Info())
if err = v.w.Write(&newPacket); err != nil {
log.Debugf("[%s] write packet error: %v, remove", v.w.Info(), err)
s.ws.Delete(key)
}
}
return true
})
}
}
func (s *Stream) TransStop() {
log.Debugf("TransStop: %s", s.info.Key)
if s.isStart && s.r != nil {
s.r.Close(fmt.Errorf("stop old"))
}
s.isStart = false
}
func (s *Stream) CheckAlive() (n int) {
if s.r != nil && s.isStart {
if s.r.Alive() {
n++
} else {
s.r.Close(fmt.Errorf("read timeout"))
}
}
s.ws.Range(func(key, val interface{}) bool {
v := val.(*PackWriterCloser)
if v.w != nil {
//Alive from RWBaser, check last frame now - timestamp, if > timeout then Remove it
if !v.w.Alive() {
log.Infof("write timeout remove")
s.ws.Delete(key)
v.w.Close(fmt.Errorf("write timeout"))
return true
}
n++
}
return true
})
return
}
func (s *Stream) closeInter() {
if s.r != nil {
s.StopStaticPush()
log.Debugf("[%v] publisher closed", s.r.Info())
}
s.ws.Range(func(key, val interface{}) bool {
v := val.(*PackWriterCloser)
if v.w != nil {
v.w.Close(fmt.Errorf("closed"))
if v.w.Info().IsInterval() {
s.ws.Delete(key)
log.Debugf("[%v] player closed and remove\n", v.w.Info())
}
}
return true
})
}
================================================
FILE: test.go
================================================
package main
================================================
FILE: utils/pio/pio.go
================================================
package pio
var RecommendBufioSize = 1024 * 64
================================================
FILE: utils/pio/reader.go
================================================
package pio
func U8(b []byte) (i uint8) {
return b[0]
}
func U16BE(b []byte) (i uint16) {
i = uint16(b[0])
i <<= 8
i |= uint16(b[1])
return
}
func I16BE(b []byte) (i int16) {
i = int16(b[0])
i <<= 8
i |= int16(b[1])
return
}
func I24BE(b []byte) (i int32) {
i = int32(int8(b[0]))
i <<= 8
i |= int32(b[1])
i <<= 8
i |= int32(b[2])
return
}
func U24BE(b []byte) (i uint32) {
i = uint32(b[0])
i <<= 8
i |= uint32(b[1])
i <<= 8
i |= uint32(b[2])
return
}
func I32BE(b []byte) (i int32) {
i = int32(int8(b[0]))
i <<= 8
i |= int32(b[1])
i <<= 8
i |= int32(b[2])
i <<= 8
i |= int32(b[3])
return
}
func U32LE(b []byte) (i uint32) {
i = uint32(b[3])
i <<= 8
i |= uint32(b[2])
i <<= 8
i |= uint32(b[1])
i <<= 8
i |= uint32(b[0])
return
}
func U32BE(b []byte) (i uint32) {
i = uint32(b[0])
i <<= 8
i |= uint32(b[1])
i <<= 8
i |= uint32(b[2])
i <<= 8
i |= uint32(b[3])
return
}
func U40BE(b []byte) (i uint64) {
i = uint64(b[0])
i <<= 8
i |= uint64(b[1])
i <<= 8
i |= uint64(b[2])
i <<= 8
i |= uint64(b[3])
i <<= 8
i |= uint64(b[4])
return
}
func U64BE(b []byte) (i uint64) {
i = uint64(b[0])
i <<= 8
i |= uint64(b[1])
i <<= 8
i |= uint64(b[2])
i <<= 8
i |= uint64(b[3])
i <<= 8
i |= uint64(b[4])
i <<= 8
i |= uint64(b[5])
i <<= 8
i |= uint64(b[6])
i <<= 8
i |= uint64(b[7])
return
}
func I64BE(b []byte) (i int64) {
i = int64(int8(b[0]))
i <<= 8
i |= int64(b[1])
i <<= 8
i |= int64(b[2])
i <<= 8
i |= int64(b[3])
i <<= 8
i |= int64(b[4])
i <<= 8
i |= int64(b[5])
i <<= 8
i |= int64(b[6])
i <<= 8
i |= int64(b[7])
return
}
================================================
FILE: utils/pio/writer.go
================================================
package pio
func PutU8(b []byte, v uint8) {
b[0] = v
}
func PutI16BE(b []byte, v int16) {
b[0] = byte(v >> 8)
b[1] = byte(v)
}
func PutU16BE(b []byte, v uint16) {
b[0] = byte(v >> 8)
b[1] = byte(v)
}
func PutI24BE(b []byte, v int32) {
b[0] = byte(v >> 16)
b[1] = byte(v >> 8)
b[2] = byte(v)
}
func PutU24BE(b []byte, v uint32) {
b[0] = byte(v >> 16)
b[1] = byte(v >> 8)
b[2] = byte(v)
}
func PutI32BE(b []byte, v int32) {
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}
func PutU32BE(b []byte, v uint32) {
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}
func PutU32LE(b []byte, v uint32) {
b[3] = byte(v >> 24)
b[2] = byte(v >> 16)
b[1] = byte(v >> 8)
b[0] = byte(v)
}
func PutU40BE(b []byte, v uint64) {
b[0] = byte(v >> 32)
b[1] = byte(v >> 24)
b[2] = byte(v >> 16)
b[3] = byte(v >> 8)
b[4] = byte(v)
}
func PutU48BE(b []byte, v uint64) {
b[0] = byte(v >> 40)
b[1] = byte(v >> 32)
b[2] = byte(v >> 24)
b[3] = byte(v >> 16)
b[4] = byte(v >> 8)
b[5] = byte(v)
}
func PutU64BE(b []byte, v uint64) {
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
func PutI64BE(b []byte, v int64) {
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
================================================
FILE: utils/pool/pool.go
================================================
package pool
type Pool struct {
pos int
buf []byte
}
const maxpoolsize = 500 * 1024
func (pool *Pool) Get(size int) []byte {
if maxpoolsize-pool.pos < size {
pool.pos = 0
pool.buf = make([]byte, maxpoolsize)
}
b := pool.buf[pool.pos : pool.pos+size]
pool.pos += size
return b
}
func NewPool() *Pool {
return &Pool{
buf: make([]byte, maxpoolsize),
}
}
================================================
FILE: utils/queue/queue.go
================================================
package queue
import (
"sync"
"github.com/gwuhaolin/livego/av"
)
// Queue is a basic FIFO queue for Messages.
type Queue struct {
maxSize int
list []*av.Packet
mutex sync.Mutex
}
// NewQueue returns a new Queue. If maxSize is greater than zero the queue will
// not grow more than the defined size.
func NewQueue(maxSize int) *Queue {
return &Queue{
maxSize: maxSize,
}
}
// Push adds a message to the queue.
func (q *Queue) Push(msg *av.Packet) {
q.mutex.Lock()
defer q.mutex.Unlock()
if len(q.list) == q.maxSize {
q.pop()
}
q.list = append(q.list, msg)
}
// Pop removes and returns a message from the queue in first to last order.
func (q *Queue) Pop() *av.Packet {
q.mutex.Lock()
defer q.mutex.Unlock()
if len(q.list) == 0 {
return nil
}
return q.pop()
}
func (q *Queue) pop() *av.Packet {
x := len(q.list) - 1
msg := q.list[x]
q.list = q.list[:x]
return msg
}
// Len returns the length of the queue.
func (q *Queue) Len() int {
q.mutex.Lock()
defer q.mutex.Unlock()
return len(q.list)
}
// All returns and removes all messages from the queue.
func (q *Queue) All() []*av.Packet {
q.mutex.Lock()
defer q.mutex.Unlock()
cache := q.list
q.list = nil
return cache
}
================================================
FILE: utils/uid/rand.go
================================================
package uid
import "math/rand"
var letterRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
================================================
FILE: utils/uid/uuid.go
================================================
package uid
import (
"encoding/base64"
"github.com/satori/go.uuid"
)
func NewId() string {
id := uuid.NewV4()
b64 := base64.URLEncoding.EncodeToString(id.Bytes()[:12])
return b64
}