Showing preview only (292K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<p align='center'>
<img src='./logo.png' width='200px' height='80px'/>
</p>
[中文](./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
================================================
<p align='center'>
<img src='./logo.png' width='200px' height='80px'/>
</p>
[](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("<key: %s, URL: %s, UID: %s, Inter: %v>",
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("<h1>push url stop %s ok</h1></br>", 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("<h1>pull url start %s ok</h1></br>", 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("<h1>session key[%s] not exist, please check it again.</h1>", keyString)
res.Data = retString
return
}
log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl)
pushRtmprelay.Stop()
delete(s.session, keyString)
retString = fmt.Sprintf("<h1>push url stop %s ok</h1></br>", 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("<h1>push url start %s ok</h1></br>", 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=<ROOM_NAME>"
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/reset?room=<ROOM_NAME>"
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=<ROOM_NAME>"
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/get?room=<ROOM_NAME>"
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=<ROOM_NAME>"
return
}
room := r.Form.Get("room")
if len(room) == 0 {
res.Status = 400
res.Data = "url: /control/delete?room=<ROOM_NAME>"
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(`<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>`)
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
=================
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
SYMBOL INDEX (690 symbols across 62 files)
FILE: av/av.go
constant TAG_AUDIO (line 9) | TAG_AUDIO = 8
constant TAG_VIDEO (line 10) | TAG_VIDEO = 9
constant TAG_SCRIPTDATAAMF0 (line 11) | TAG_SCRIPTDATAAMF0 = 18
constant TAG_SCRIPTDATAAMF3 (line 12) | TAG_SCRIPTDATAAMF3 = 0xf
constant MetadatAMF0 (line 16) | MetadatAMF0 = 0x12
constant MetadataAMF3 (line 17) | MetadataAMF3 = 0xf
constant SOUND_MP3 (line 21) | SOUND_MP3 = 2
constant SOUND_NELLYMOSER_16KHZ_MONO (line 22) | SOUND_NELLYMOSER_16KHZ_MONO = 4
constant SOUND_NELLYMOSER_8KHZ_MONO (line 23) | SOUND_NELLYMOSER_8KHZ_MONO = 5
constant SOUND_NELLYMOSER (line 24) | SOUND_NELLYMOSER = 6
constant SOUND_ALAW (line 25) | SOUND_ALAW = 7
constant SOUND_MULAW (line 26) | SOUND_MULAW = 8
constant SOUND_AAC (line 27) | SOUND_AAC = 10
constant SOUND_SPEEX (line 28) | SOUND_SPEEX = 11
constant SOUND_5_5Khz (line 30) | SOUND_5_5Khz = 0
constant SOUND_11Khz (line 31) | SOUND_11Khz = 1
constant SOUND_22Khz (line 32) | SOUND_22Khz = 2
constant SOUND_44Khz (line 33) | SOUND_44Khz = 3
constant SOUND_8BIT (line 35) | SOUND_8BIT = 0
constant SOUND_16BIT (line 36) | SOUND_16BIT = 1
constant SOUND_MONO (line 38) | SOUND_MONO = 0
constant SOUND_STEREO (line 39) | SOUND_STEREO = 1
constant AAC_SEQHDR (line 41) | AAC_SEQHDR = 0
constant AAC_RAW (line 42) | AAC_RAW = 1
constant AVC_SEQHDR (line 46) | AVC_SEQHDR = 0
constant AVC_NALU (line 47) | AVC_NALU = 1
constant AVC_EOS (line 48) | AVC_EOS = 2
constant FRAME_KEY (line 50) | FRAME_KEY = 1
constant FRAME_INTER (line 51) | FRAME_INTER = 2
constant VIDEO_H264 (line 53) | VIDEO_H264 = 7
type Packet (line 62) | type Packet struct
type PacketHeader (line 72) | type PacketHeader interface
type AudioPacketHeader (line 75) | type AudioPacketHeader interface
type VideoPacketHeader (line 81) | type VideoPacketHeader interface
type Demuxer (line 89) | type Demuxer interface
type Muxer (line 93) | type Muxer interface
type SampleRater (line 97) | type SampleRater interface
type CodecParser (line 101) | type CodecParser interface
type GetWriter (line 106) | type GetWriter interface
type Handler (line 110) | type Handler interface
type Alive (line 115) | type Alive interface
type Closer (line 119) | type Closer interface
type CalcTime (line 124) | type CalcTime interface
type Info (line 128) | type Info struct
method IsInterval (line 135) | func (info Info) IsInterval() bool {
method String (line 139) | func (info Info) String() string {
type ReadCloser (line 144) | type ReadCloser interface
type WriteCloser (line 150) | type WriteCloser interface
FILE: av/rwbase.go
type RWBaser (line 8) | type RWBaser struct
method BaseTimeStamp (line 24) | func (rw *RWBaser) BaseTimeStamp() uint32 {
method CalcBaseTimestamp (line 28) | func (rw *RWBaser) CalcBaseTimestamp() {
method RecTimeStamp (line 36) | func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) {
method SetPreTime (line 44) | func (rw *RWBaser) SetPreTime() {
method Alive (line 50) | func (rw *RWBaser) Alive() bool {
function NewRWBaser (line 17) | func NewRWBaser(duration time.Duration) RWBaser {
FILE: configure/channel.go
type RoomKeysType (line 13) | type RoomKeysType struct
method SetKey (line 45) | func (r *RoomKeysType) SetKey(channel string) (key string, err error) {
method GetKey (line 74) | func (r *RoomKeysType) GetKey(channel string) (newKey string, err erro...
method GetChannel (line 95) | func (r *RoomKeysType) GetChannel(key string) (channel string, err err...
method DeleteChannel (line 108) | func (r *RoomKeysType) DeleteChannel(channel string) bool {
method DeleteKey (line 122) | func (r *RoomKeysType) DeleteKey(key string) bool {
function Init (line 24) | func Init() {
FILE: configure/liveconfig.go
type Application (line 27) | type Application struct
type Applications (line 36) | type Applications
type JWT (line 38) | type JWT struct
type ServerCfg (line 42) | type ServerCfg struct
function initLog (line 97) | func initLog() {
function init (line 104) | func init() {
function initDefault (line 110) | func initDefault() {
function CheckAppName (line 165) | func CheckAppName(appname string) bool {
function GetStaticPushUrlList (line 176) | func GetStaticPushUrlList(appname string) ([]string, bool) {
FILE: container/flv/demuxer.go
type Demuxer (line 12) | type Demuxer struct
method DemuxH (line 19) | func (d *Demuxer) DemuxH(p *av.Packet) error {
method Demux (line 30) | func (d *Demuxer) Demux(p *av.Packet) error {
function NewDemuxer (line 15) | func NewDemuxer() *Demuxer {
FILE: container/flv/muxer.go
constant headerLen (line 49) | headerLen = 11
type FLVWriter (line 52) | type FLVWriter struct
method Write (line 81) | func (writer *FLVWriter) Write(p *av.Packet) error {
method Wait (line 127) | func (writer *FLVWriter) Wait() {
method Close (line 134) | func (writer *FLVWriter) Close(error) {
method Info (line 143) | func (writer *FLVWriter) Info() (ret av.Info) {
function NewFLVWriter (line 62) | func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter {
type FlvDvr (line 150) | type FlvDvr struct
method GetWriter (line 152) | func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser {
FILE: container/flv/tag.go
type flvTag (line 9) | type flvTag struct
type mediaTag (line 16) | type mediaTag struct
type Tag (line 104) | type Tag struct
method SoundFormat (line 109) | func (tag *Tag) SoundFormat() uint8 {
method AACPacketType (line 113) | func (tag *Tag) AACPacketType() uint8 {
method IsKeyFrame (line 117) | func (tag *Tag) IsKeyFrame() bool {
method IsSeq (line 121) | func (tag *Tag) IsSeq() bool {
method CodecID (line 126) | func (tag *Tag) CodecID() uint8 {
method CompositionTime (line 130) | func (tag *Tag) CompositionTime() int32 {
method ParseMediaTagHeader (line 135) | func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, er...
method parseAudioHeader (line 145) | func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) {
method parseVideoHeader (line 164) | func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) {
FILE: container/ts/crc32.go
function GenCrc32 (line 3) | func GenCrc32(src []byte) uint32 {
FILE: container/ts/muxer.go
constant tsDefaultDataLen (line 10) | tsDefaultDataLen = 184
constant tsPacketLen (line 11) | tsPacketLen = 188
constant h264DefaultHZ (line 12) | h264DefaultHZ = 90
constant videoPID (line 14) | videoPID = 0x100
constant audioPID (line 15) | audioPID = 0x101
constant videoSID (line 16) | videoSID = 0xe0
constant audioSID (line 17) | audioSID = 0xc0
type Muxer (line 20) | type Muxer struct
method Mux (line 34) | func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error {
method PAT (line 162) | func (muxer *Muxer) PAT() []byte {
method PMT (line 199) | func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte {
method adaptationBufInit (line 258) | func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) {
method writePcr (line 270) | func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error {
function NewMuxer (line 30) | func NewMuxer() *Muxer {
type pesHeader (line 286) | type pesHeader struct
method packet (line 292) | func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error {
method writeTs (line 345) | func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) {
FILE: container/ts/muxer_test.go
type TestWriter (line 11) | type TestWriter struct
method Write (line 17) | func (w *TestWriter) Write(p []byte) (int, error) {
function TestTSEncoder (line 23) | func TestTSEncoder(t *testing.T) {
FILE: main.go
function startHls (line 22) | func startHls() *hls.Server {
function startRtmp (line 42) | func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) {
function startHTTPFlv (line 92) | func startHTTPFlv(stream *rtmp.RtmpStream) {
function startAPI (line 112) | func startAPI(stream *rtmp.RtmpStream) {
function init (line 134) | func init() {
function main (line 144) | func main() {
FILE: parser/aac/parser.go
type mpegExtension (line 10) | type mpegExtension struct
type mpegCfgInfo (line 15) | type mpegCfgInfo struct
constant adtsHeaderLen (line 34) | adtsHeaderLen = 7
type Parser (line 37) | type Parser struct
method specificInfo (line 51) | func (parser *Parser) specificInfo(src []byte) error {
method adts (line 62) | func (parser *Parser) adts(src []byte, w io.Writer) error {
method SampleRate (line 98) | func (parser *Parser) SampleRate() int {
method Parse (line 106) | func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (...
function NewParser (line 43) | func NewParser() *Parser {
FILE: parser/h264/parser.go
constant i_frame (line 10) | i_frame byte = 0
constant p_frame (line 11) | p_frame byte = 1
constant b_frame (line 12) | b_frame byte = 2
constant nalu_type_not_define (line 16) | nalu_type_not_define byte = 0
constant nalu_type_slice (line 17) | nalu_type_slice byte = 1
constant nalu_type_dpa (line 18) | nalu_type_dpa byte = 2
constant nalu_type_dpb (line 19) | nalu_type_dpb byte = 3
constant nalu_type_dpc (line 20) | nalu_type_dpc byte = 4
constant nalu_type_idr (line 21) | nalu_type_idr byte = 5
constant nalu_type_sei (line 22) | nalu_type_sei byte = 6
constant nalu_type_sps (line 23) | nalu_type_sps byte = 7
constant nalu_type_pps (line 24) | nalu_type_pps byte = 8
constant nalu_type_aud (line 25) | nalu_type_aud byte = 9
constant nalu_type_eoesq (line 26) | nalu_type_eoesq byte = 10
constant nalu_type_eostream (line 27) | nalu_type_eostream byte = 11
constant nalu_type_filler (line 28) | nalu_type_filler byte = 12
constant naluBytesLen (line 32) | naluBytesLen int = 4
constant maxSpsPpsLen (line 33) | maxSpsPpsLen int = 2 * 1024
type Parser (line 50) | type Parser struct
method parseSpecificInfo (line 77) | func (parser *Parser) parseSpecificInfo(src []byte) error {
method isNaluHeader (line 123) | func (parser *Parser) isNaluHeader(src []byte) bool {
method naluSize (line 133) | func (parser *Parser) naluSize(src []byte) (int, error) {
method getAnnexbH264 (line 145) | func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error {
method Parse (line 219) | func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err er...
type sequenceHeader (line 56) | type sequenceHeader struct
function NewParser (line 70) | func NewParser() *Parser {
FILE: parser/h264/parser_test.go
function TestH264SeqDemux (line 11) | func TestH264SeqDemux(t *testing.T) {
function TestH264AnnexbDemux (line 28) | func TestH264AnnexbDemux(t *testing.T) {
function TestH264NalueSizeException (line 42) | func TestH264NalueSizeException(t *testing.T) {
function TestH264Mp4Demux (line 53) | func TestH264Mp4Demux(t *testing.T) {
function TestH264Mp4DemuxException1 (line 70) | func TestH264Mp4DemuxException1(t *testing.T) {
function TestH264Mp4DemuxException2 (line 82) | func TestH264Mp4DemuxException2(t *testing.T) {
FILE: parser/mp3/parser.go
type Parser (line 7) | type Parser struct
method Parse (line 26) | func (parser *Parser) Parse(src []byte) error {
method SampleRate (line 38) | func (parser *Parser) SampleRate() int {
function NewParser (line 11) | func NewParser() *Parser {
FILE: parser/parser.go
type CodecParser (line 17) | type CodecParser struct
method SampleRate (line 27) | func (codeParser *CodecParser) SampleRate() (int, error) {
method Parse (line 37) | func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err e...
function NewCodecParser (line 23) | func NewCodecParser() *CodecParser {
FILE: protocol/amf/amf.go
method DecodeBatch (line 8) | func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface...
method Decode (line 20) | func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {
method EncodeBatch (line 31) | func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface...
method Encode (line 40) | func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int...
FILE: protocol/amf/amf_test.go
function EncodeAndDecode (line 11) | func EncodeAndDecode(val interface{}, ver Version) (result interface{}, ...
function Compare (line 30) | func Compare(val interface{}, ver Version, name string, t *testing.T) {
function TestAmf0Number (line 51) | func TestAmf0Number(t *testing.T) {
function TestAmf0String (line 57) | func TestAmf0String(t *testing.T) {
function TestAmf0Boolean (line 62) | func TestAmf0Boolean(t *testing.T) {
function TestAmf0Null (line 67) | func TestAmf0Null(t *testing.T) {
function TestAmf0Object (line 71) | func TestAmf0Object(t *testing.T) {
function TestAmf0Array (line 105) | func TestAmf0Array(t *testing.T) {
function TestAmf3Integer (line 125) | func TestAmf3Integer(t *testing.T) {
function TestAmf3Double (line 131) | func TestAmf3Double(t *testing.T) {
function TestAmf3String (line 137) | func TestAmf3String(t *testing.T) {
function TestAmf3Boolean (line 142) | func TestAmf3Boolean(t *testing.T) {
function TestAmf3Null (line 147) | func TestAmf3Null(t *testing.T) {
function TestAmf3Date (line 151) | func TestAmf3Date(t *testing.T) {
function TestAmf3Array (line 159) | func TestAmf3Array(t *testing.T) {
function TestAmf3ByteArray (line 187) | func TestAmf3ByteArray(t *testing.T) {
FILE: protocol/amf/const.go
constant AMF0 (line 8) | AMF0 = 0x00
constant AMF3 (line 9) | AMF3 = 0x03
constant AMF0_NUMBER_MARKER (line 13) | AMF0_NUMBER_MARKER = 0x00
constant AMF0_BOOLEAN_MARKER (line 14) | AMF0_BOOLEAN_MARKER = 0x01
constant AMF0_STRING_MARKER (line 15) | AMF0_STRING_MARKER = 0x02
constant AMF0_OBJECT_MARKER (line 16) | AMF0_OBJECT_MARKER = 0x03
constant AMF0_MOVIECLIP_MARKER (line 17) | AMF0_MOVIECLIP_MARKER = 0x04
constant AMF0_NULL_MARKER (line 18) | AMF0_NULL_MARKER = 0x05
constant AMF0_UNDEFINED_MARKER (line 19) | AMF0_UNDEFINED_MARKER = 0x06
constant AMF0_REFERENCE_MARKER (line 20) | AMF0_REFERENCE_MARKER = 0x07
constant AMF0_ECMA_ARRAY_MARKER (line 21) | AMF0_ECMA_ARRAY_MARKER = 0x08
constant AMF0_OBJECT_END_MARKER (line 22) | AMF0_OBJECT_END_MARKER = 0x09
constant AMF0_STRICT_ARRAY_MARKER (line 23) | AMF0_STRICT_ARRAY_MARKER = 0x0a
constant AMF0_DATE_MARKER (line 24) | AMF0_DATE_MARKER = 0x0b
constant AMF0_LONG_STRING_MARKER (line 25) | AMF0_LONG_STRING_MARKER = 0x0c
constant AMF0_UNSUPPORTED_MARKER (line 26) | AMF0_UNSUPPORTED_MARKER = 0x0d
constant AMF0_RECORDSET_MARKER (line 27) | AMF0_RECORDSET_MARKER = 0x0e
constant AMF0_XML_DOCUMENT_MARKER (line 28) | AMF0_XML_DOCUMENT_MARKER = 0x0f
constant AMF0_TYPED_OBJECT_MARKER (line 29) | AMF0_TYPED_OBJECT_MARKER = 0x10
constant AMF0_ACMPLUS_OBJECT_MARKER (line 30) | AMF0_ACMPLUS_OBJECT_MARKER = 0x11
constant AMF0_BOOLEAN_FALSE (line 34) | AMF0_BOOLEAN_FALSE = 0x00
constant AMF0_BOOLEAN_TRUE (line 35) | AMF0_BOOLEAN_TRUE = 0x01
constant AMF0_STRING_MAX (line 36) | AMF0_STRING_MAX = 65535
constant AMF3_INTEGER_MAX (line 37) | AMF3_INTEGER_MAX = 536870911
constant AMF3_UNDEFINED_MARKER (line 41) | AMF3_UNDEFINED_MARKER = 0x00
constant AMF3_NULL_MARKER (line 42) | AMF3_NULL_MARKER = 0x01
constant AMF3_FALSE_MARKER (line 43) | AMF3_FALSE_MARKER = 0x02
constant AMF3_TRUE_MARKER (line 44) | AMF3_TRUE_MARKER = 0x03
constant AMF3_INTEGER_MARKER (line 45) | AMF3_INTEGER_MARKER = 0x04
constant AMF3_DOUBLE_MARKER (line 46) | AMF3_DOUBLE_MARKER = 0x05
constant AMF3_STRING_MARKER (line 47) | AMF3_STRING_MARKER = 0x06
constant AMF3_XMLDOC_MARKER (line 48) | AMF3_XMLDOC_MARKER = 0x07
constant AMF3_DATE_MARKER (line 49) | AMF3_DATE_MARKER = 0x08
constant AMF3_ARRAY_MARKER (line 50) | AMF3_ARRAY_MARKER = 0x09
constant AMF3_OBJECT_MARKER (line 51) | AMF3_OBJECT_MARKER = 0x0a
constant AMF3_XMLSTRING_MARKER (line 52) | AMF3_XMLSTRING_MARKER = 0x0b
constant AMF3_BYTEARRAY_MARKER (line 53) | AMF3_BYTEARRAY_MARKER = 0x0c
type ExternalHandler (line 56) | type ExternalHandler
type Decoder (line 58) | type Decoder struct
method RegisterExternalHandler (line 72) | func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandl...
function NewDecoder (line 66) | func NewDecoder() *Decoder {
type Encoder (line 76) | type Encoder struct
type Version (line 79) | type Version
type Array (line 81) | type Array
type Object (line 82) | type Object
type TypedObject (line 84) | type TypedObject struct
type Trait (line 89) | type Trait struct
function NewTrait (line 96) | func NewTrait() *Trait {
function NewTypedObject (line 100) | func NewTypedObject() *TypedObject {
FILE: protocol/amf/decoder_amf0.go
method DecodeAmf0 (line 10) | func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
method DecodeAmf0Number (line 58) | func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (resu...
method DecodeAmf0Boolean (line 73) | func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (res...
method DecodeAmf0String (line 96) | func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (resu...
method DecodeAmf0Object (line 119) | func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Obje...
method DecodeAmf0Null (line 155) | func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result...
method DecodeAmf0Undefined (line 162) | func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (r...
method DecodeAmf0EcmaArray (line 199) | func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (O...
method DecodeAmf0StrictArray (line 219) | func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) ...
method DecodeAmf0Date (line 248) | func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result...
method DecodeAmf0LongString (line 268) | func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (...
method DecodeAmf0Unsupported (line 289) | func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) ...
method DecodeAmf0XmlDocument (line 299) | func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) ...
method DecodeAmf0TypedObject (line 315) | func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) ...
FILE: protocol/amf/decoder_amf0_test.go
function TestDecodeAmf0Number (line 8) | func TestDecodeAmf0Number(t *testing.T) {
function TestDecodeAmf0BooleanTrue (line 44) | func TestDecodeAmf0BooleanTrue(t *testing.T) {
function TestDecodeAmf0BooleanFalse (line 80) | func TestDecodeAmf0BooleanFalse(t *testing.T) {
function TestDecodeAmf0String (line 116) | func TestDecodeAmf0String(t *testing.T) {
function TestDecodeAmf0Object (line 152) | func TestDecodeAmf0Object(t *testing.T) {
function TestDecodeAmf0Null (line 199) | func TestDecodeAmf0Null(t *testing.T) {
function TestDecodeAmf0Undefined (line 224) | func TestDecodeAmf0Undefined(t *testing.T) {
function TestDecodeAmf0EcmaArray (line 271) | func TestDecodeAmf0EcmaArray(t *testing.T) {
function TestDecodeAmf0StrictArray (line 318) | func TestDecodeAmf0StrictArray(t *testing.T) {
function TestDecodeAmf0Date (line 383) | func TestDecodeAmf0Date(t *testing.T) {
function TestDecodeAmf0LongString (line 419) | func TestDecodeAmf0LongString(t *testing.T) {
function TestDecodeAmf0Unsupported (line 455) | func TestDecodeAmf0Unsupported(t *testing.T) {
function TestDecodeAmf0XmlDocument (line 480) | func TestDecodeAmf0XmlDocument(t *testing.T) {
function TestDecodeAmf0TypedObject (line 516) | func TestDecodeAmf0TypedObject(t *testing.T) {
FILE: protocol/amf/decoder_amf3.go
method DecodeAmf3 (line 11) | func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) {
method DecodeAmf3Undefined (line 51) | func (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (r...
method DecodeAmf3Null (line 58) | func (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result...
method DecodeAmf3False (line 65) | func (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (resul...
method DecodeAmf3True (line 73) | func (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result...
method DecodeAmf3Integer (line 80) | func (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (res...
method DecodeAmf3Double (line 100) | func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (resu...
method DecodeAmf3String (line 117) | func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (resu...
method DecodeAmf3Date (line 152) | func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result...
method DecodeAmf3Array (line 191) | func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (resul...
method DecodeAmf3Object (line 239) | func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (resu...
method DecodeAmf3Xml (line 380) | func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result ...
method DecodeAmf3ByteArray (line 430) | func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (r...
method decodeU29 (line 463) | func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {
method decodeReferenceInt (line 487) | func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal ui...
FILE: protocol/amf/decoder_amf3_external.go
method decodeAbstractMessage (line 10) | func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err...
method decodeAsyncMessageExt (line 23) | func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err...
method decodeAsyncMessage (line 26) | func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err er...
method decodeAcknowledgeMessageExt (line 40) | func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Objec...
method decodeAcknowledgeMessage (line 43) | func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, ...
method decodeArrayCollection (line 57) | func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {
method decodeExternal (line 66) | func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ......
function readFlags (line 113) | func readFlags(r io.Reader) (result []uint8, err error) {
FILE: protocol/amf/decoder_amf3_test.go
type u29TestCase (line 8) | type u29TestCase struct
function TestDecodeAmf3Undefined (line 31) | func TestDecodeAmf3Undefined(t *testing.T) {
function TestDecodeAmf3Null (line 45) | func TestDecodeAmf3Null(t *testing.T) {
function TestDecodeAmf3False (line 59) | func TestDecodeAmf3False(t *testing.T) {
function TestDecodeAmf3True (line 74) | func TestDecodeAmf3True(t *testing.T) {
function TestDecodeU29 (line 89) | func TestDecodeU29(t *testing.T) {
function TestDecodeAmf3Integer (line 104) | func TestDecodeAmf3Integer(t *testing.T) {
function TestDecodeAmf3Double (line 137) | func TestDecodeAmf3Double(t *testing.T) {
function TestDecodeAmf3String (line 152) | func TestDecodeAmf3String(t *testing.T) {
function TestDecodeAmf3Array (line 167) | func TestDecodeAmf3Array(t *testing.T) {
function TestDecodeAmf3Object (line 194) | func TestDecodeAmf3Object(t *testing.T) {
FILE: protocol/amf/encoder_amf0.go
method EncodeAmf0 (line 11) | func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) {
method EncodeAmf0Number (line 61) | func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarke...
method EncodeAmf0Boolean (line 80) | func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker ...
method EncodeAmf0String (line 109) | func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker...
method EncodeAmf0Object (line 138) | func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker...
method EncodeAmf0Null (line 178) | func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int,...
method EncodeAmf0Undefined (line 191) | func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n...
method EncodeAmf0EcmaArray (line 208) | func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMar...
method EncodeAmf0StrictArray (line 237) | func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMa...
method EncodeAmf0LongString (line 268) | func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMa...
method EncodeAmf0Unsupported (line 295) | func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) ...
method EncodeAmf0Amf3Marker (line 307) | func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error {
FILE: protocol/amf/encoder_amf0_test.go
function TestEncodeAmf0Number (line 9) | func TestEncodeAmf0Number(t *testing.T) {
function TestEncodeAmf0BooleanTrue (line 27) | func TestEncodeAmf0BooleanTrue(t *testing.T) {
function TestEncodeAmf0BooleanFalse (line 45) | func TestEncodeAmf0BooleanFalse(t *testing.T) {
function TestEncodeAmf0String (line 63) | func TestEncodeAmf0String(t *testing.T) {
function TestEncodeAmf0Object (line 81) | func TestEncodeAmf0Object(t *testing.T) {
function TestEncodeAmf0EcmaArray (line 102) | func TestEncodeAmf0EcmaArray(t *testing.T) {
function TestEncodeAmf0StrictArray (line 121) | func TestEncodeAmf0StrictArray(t *testing.T) {
function TestEncodeAmf0Null (line 142) | func TestEncodeAmf0Null(t *testing.T) {
function TestEncodeAmf0LongString (line 160) | func TestEncodeAmf0LongString(t *testing.T) {
FILE: protocol/amf/encoder_amf3.go
method EncodeAmf3 (line 14) | func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
method EncodeAmf3Undefined (line 85) | func (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n...
method EncodeAmf3Null (line 98) | func (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int,...
method EncodeAmf3False (line 111) | func (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int...
method EncodeAmf3True (line 124) | func (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int,...
method EncodeAmf3Integer (line 136) | func (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarke...
method EncodeAmf3Double (line 155) | func (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarke...
method EncodeAmf3String (line 176) | func (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker...
method EncodeAmf3Date (line 199) | func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarke...
method EncodeAmf3Array (line 227) | func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker b...
method EncodeAmf3Object (line 264) | func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeM...
method EncodeAmf3ByteArray (line 367) | func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMar...
method encodeAmf3Utf8 (line 395) | func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err er...
method encodeAmf3Uint29 (line 415) | func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err ...
FILE: protocol/amf/encoder_amf3_test.go
function TestEncodeAmf3EmptyString (line 8) | func TestEncodeAmf3EmptyString(t *testing.T) {
function TestEncodeAmf3Undefined (line 24) | func TestEncodeAmf3Undefined(t *testing.T) {
function TestEncodeAmf3Null (line 40) | func TestEncodeAmf3Null(t *testing.T) {
function TestEncodeAmf3False (line 56) | func TestEncodeAmf3False(t *testing.T) {
function TestEncodeAmf3True (line 72) | func TestEncodeAmf3True(t *testing.T) {
function TestEncodeAmf3Integer (line 88) | func TestEncodeAmf3Integer(t *testing.T) {
function TestEncodeAmf3Double (line 118) | func TestEncodeAmf3Double(t *testing.T) {
function TestEncodeAmf3String (line 134) | func TestEncodeAmf3String(t *testing.T) {
function TestEncodeAmf3Array (line 150) | func TestEncodeAmf3Array(t *testing.T) {
function TestEncodeAmf3Object (line 176) | func TestEncodeAmf3Object(t *testing.T) {
FILE: protocol/amf/metadata.go
constant ADD (line 11) | ADD = 0x0
constant DEL (line 12) | DEL = 0x3
constant SetDataFrame (line 16) | SetDataFrame string = "@setDataFrame"
constant OnMetaData (line 17) | OnMetaData string = "onMetaData"
function init (line 22) | func init() {
function MetaDataReform (line 31) | func MetaDataReform(p []byte, flag uint8) ([]byte, error) {
FILE: protocol/amf/util.go
function DumpBytes (line 9) | func DumpBytes(label string, buf []byte, size int) {
function Dump (line 17) | func Dump(label string, val interface{}) error {
function WriteByte (line 27) | func WriteByte(w io.Writer, b byte) (err error) {
function WriteBytes (line 36) | func WriteBytes(w io.Writer, bytes []byte) (int, error) {
function ReadByte (line 40) | func ReadByte(r io.Reader) (byte, error) {
function ReadBytes (line 49) | func ReadBytes(r io.Reader, n int) ([]byte, error) {
function WriteMarker (line 64) | func WriteMarker(w io.Writer, m byte) error {
function ReadMarker (line 68) | func ReadMarker(r io.Reader) (byte, error) {
function AssertMarker (line 72) | func AssertMarker(r io.Reader, checkMarker bool, m byte) error {
FILE: protocol/api/api.go
type Response (line 19) | type Response struct
method SendJson (line 25) | func (r *Response) SendJson() (int, error) {
type Operation (line 32) | type Operation struct
type OperationChange (line 38) | type OperationChange struct
type ClientInfo (line 45) | type ClientInfo struct
type Server (line 51) | type Server struct
method Serve (line 103) | func (s *Server) Serve(l net.Listener) error {
method GetLiveStatics (line 146) | func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http....
method handlePull (line 246) | func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) {
method handlePush (line 315) | func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) {
method handleReset (line 378) | func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
method handleGet (line 410) | func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
method handleDelete (line 441) | func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {
function NewServer (line 57) | func NewServer(h av.Handler, rtmpAddr string) *Server {
function JWTMiddleware (line 65) | func JWTMiddleware(next http.Handler) http.Handler {
type stream (line 130) | type stream struct
type streams (line 140) | type streams struct
FILE: protocol/hls/align.go
constant syncms (line 4) | syncms = 2
type align (line 7) | type align struct
method align (line 12) | func (a *align) align(dts *uint64, inc uint32) {
FILE: protocol/hls/audio_cache.go
constant cache_max_frames (line 6) | cache_max_frames byte = 6
constant audio_cache_len (line 7) | audio_cache_len int = 10 * 1024
type audioCache (line 10) | type audioCache struct
method Cache (line 24) | func (a *audioCache) Cache(src []byte, pts uint64) bool {
method GetFrame (line 37) | func (a *audioCache) GetFrame() (int, uint64, []byte) {
method CacheNum (line 42) | func (a *audioCache) CacheNum() byte {
function newAudioCache (line 18) | func newAudioCache() *audioCache {
FILE: protocol/hls/cache.go
constant maxTSCacheNum (line 11) | maxTSCacheNum = 3
type TSCacheItem (line 18) | type TSCacheItem struct
method ID (line 35) | func (tcCacheItem *TSCacheItem) ID() string {
method GenM3U8PlayList (line 40) | func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) {
method SetItem (line 67) | func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) {
method GetItem (line 78) | func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) {
function NewTSCacheItem (line 26) | func NewTSCacheItem(id string) *TSCacheItem {
FILE: protocol/hls/hls.go
constant duration (line 21) | duration = 3000
type Server (line 37) | type Server struct
method Serve (line 50) | func (server *Server) Serve(listener net.Listener) error {
method GetWriter (line 66) | func (server *Server) GetWriter(info av.Info) av.WriteCloser {
method getConn (line 79) | func (server *Server) getConn(key string) *Source {
method checkStop (line 87) | func (server *Server) checkStop() {
method handle (line 102) | func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
method parseM3u8 (line 154) | func (server *Server) parseM3u8(pathstr string) (key string, err error) {
method parseTs (line 160) | func (server *Server) parseTs(pathstr string) (key string, err error) {
function NewServer (line 42) | func NewServer() *Server {
FILE: protocol/hls/item.go
type TSItem (line 3) | type TSItem struct
function NewTSItem (line 10) | func NewTSItem(name string, duration, seqNum int, b []byte) TSItem {
FILE: protocol/hls/source.go
constant videoHZ (line 18) | videoHZ = 90000
constant aacSampleLen (line 19) | aacSampleLen = 1024
constant maxQueueNum (line 20) | maxQueueNum = 512
constant h264_default_hz (line 22) | h264_default_hz uint64 = 90
type Source (line 25) | type Source struct
method GetCacheInc (line 68) | func (source *Source) GetCacheInc() *TSCacheItem {
method DropPacket (line 72) | func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
method Write (line 100) | func (source *Source) Write(p *av.Packet) (err error) {
method SendPacket (line 122) | func (source *Source) SendPacket() error {
method Info (line 170) | func (source *Source) Info() (ret av.Info) {
method cleanup (line 174) | func (source *Source) cleanup() {
method Close (line 182) | func (source *Source) Close(err error) {
method cut (line 190) | func (source *Source) cut() {
method parse (line 213) | func (source *Source) parse(p *av.Packet) (int32, bool, error) {
method calcPtsDts (line 247) | func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint3...
method flushAudio (line 257) | func (source *Source) flushAudio() error {
method muxAudio (line 261) | func (source *Source) muxAudio(limit byte) error {
method tsMux (line 272) | func (source *Source) tsMux(p *av.Packet) error {
function NewSource (line 43) | func NewSource(info av.Info) *Source {
FILE: protocol/hls/status.go
type status (line 5) | type status struct
method update (line 23) | func (t *status) update(isVideo bool, timestamp uint32) {
method resetAndNew (line 34) | func (t *status) resetAndNew() {
method durationMs (line 41) | func (t *status) durationMs() int64 {
function newStatus (line 15) | func newStatus() *status {
FILE: protocol/httpflv/server.go
type Server (line 15) | type Server struct
method Serve (line 35) | func (server *Server) Serve(l net.Listener) error {
method getStreams (line 50) | func (server *Server) getStreams(w http.ResponseWriter, r *http.Reques...
method getStream (line 85) | func (server *Server) getStream(w http.ResponseWriter, r *http.Request) {
method handleConn (line 95) | func (server *Server) handleConn(w http.ResponseWriter, r *http.Reques...
type stream (line 19) | type stream struct
type streams (line 24) | type streams struct
function NewServer (line 29) | func NewServer(h av.Handler) *Server {
FILE: protocol/httpflv/writer.go
constant headerLen (line 17) | headerLen = 11
constant maxQueueNum (line 18) | maxQueueNum = 1024
type FLVWriter (line 21) | type FLVWriter struct
method DropPacket (line 65) | func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av...
method Write (line 92) | func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) {
method SendPacket (line 114) | func (flvWriter *FLVWriter) SendPacket() error {
method Wait (line 166) | func (flvWriter *FLVWriter) Wait() {
method Close (line 173) | func (flvWriter *FLVWriter) Close(error) {
method Info (line 182) | func (flvWriter *FLVWriter) Info() (ret av.Info) {
function NewFLVWriter (line 32) | func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVW...
FILE: protocol/rtmp/cache/cache.go
type Cache (line 8) | type Cache struct
method Write (line 24) | func (cache *Cache) Write(p av.Packet) {
method Send (line 57) | func (cache *Cache) Send(w av.WriteCloser) error {
function NewCache (line 15) | func NewCache() *Cache {
FILE: protocol/rtmp/cache/gop.go
type array (line 14) | type array struct
method reset (line 27) | func (array *array) reset() {
method write (line 32) | func (array *array) write(packet *av.Packet) error {
method send (line 41) | func (array *array) send(w av.WriteCloser) error {
function newArray (line 19) | func newArray() *array {
type GopCache (line 52) | type GopCache struct
method writeToArray (line 67) | func (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool...
method Write (line 87) | func (gopCache *GopCache) Write(p *av.Packet) {
method sendTo (line 101) | func (gopCache *GopCache) sendTo(w av.WriteCloser) error {
method Send (line 118) | func (gopCache *GopCache) Send(w av.WriteCloser) error {
function NewGopCache (line 60) | func NewGopCache(num int) *GopCache {
FILE: protocol/rtmp/cache/special.go
constant SetDataFrame (line 13) | SetDataFrame string = "@setDataFrame"
constant OnMetaData (line 14) | OnMetaData string = "onMetaData"
function init (line 19) | func init() {
type SpecialCache (line 28) | type SpecialCache struct
method Write (line 37) | func (specialCache *SpecialCache) Write(p *av.Packet) {
method Send (line 42) | func (specialCache *SpecialCache) Send(w av.WriteCloser) error {
function NewSpecialCache (line 33) | func NewSpecialCache() *SpecialCache {
FILE: protocol/rtmp/core/chunk_stream.go
type ChunkStream (line 11) | type ChunkStream struct
method full (line 27) | func (chunkStream *ChunkStream) full() bool {
method new (line 31) | func (chunkStream *ChunkStream) new(pool *pool.Pool) {
method writeHeader (line 38) | func (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error {
method writeChunk (line 83) | func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize in...
method readChunk (line 123) | func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uin...
FILE: protocol/rtmp/core/chunk_stream_test.go
function TestChunkRead1 (line 12) | func TestChunkRead1(t *testing.T) {
function TestWriteChunk (line 82) | func TestWriteChunk(t *testing.T) {
FILE: protocol/rtmp/core/conn.go
constant _ (line 13) | _ = iota
constant idSetChunkSize (line 14) | idSetChunkSize
constant idAbortMessage (line 15) | idAbortMessage
constant idAck (line 16) | idAck
constant idUserControlMessages (line 17) | idUserControlMessages
constant idWindowAckSize (line 18) | idWindowAckSize
constant idSetPeerBandwidth (line 19) | idSetPeerBandwidth
type Conn (line 22) | type Conn struct
method Read (line 48) | func (conn *Conn) Read(c *ChunkStream) error {
method Write (line 82) | func (conn *Conn) Write(c *ChunkStream) error {
method Flush (line 89) | func (conn *Conn) Flush() error {
method Close (line 93) | func (conn *Conn) Close() error {
method RemoteAddr (line 97) | func (conn *Conn) RemoteAddr() net.Addr {
method LocalAddr (line 101) | func (conn *Conn) LocalAddr() net.Addr {
method SetDeadline (line 105) | func (conn *Conn) SetDeadline(t time.Time) error {
method NewAck (line 109) | func (conn *Conn) NewAck(size uint32) ChunkStream {
method NewSetChunkSize (line 113) | func (conn *Conn) NewSetChunkSize(size uint32) ChunkStream {
method NewWindowAckSize (line 117) | func (conn *Conn) NewWindowAckSize(size uint32) ChunkStream {
method NewSetPeerBandwidth (line 121) | func (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream {
method handleControlMsg (line 127) | func (conn *Conn) handleControlMsg(c *ChunkStream) {
method ack (line 135) | func (conn *Conn) ack(size uint32) {
method userControlMsg (line 177) | func (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream {
method SetBegin (line 193) | func (conn *Conn) SetBegin() {
method SetRecorded (line 201) | func (conn *Conn) SetRecorded() {
function NewConn (line 35) | func NewConn(c net.Conn, bufferSize int) *Conn {
function initControlMsg (line 148) | func initControlMsg(id, size, value uint32) ChunkStream {
constant streamBegin (line 162) | streamBegin uint32 = 0
constant streamEOF (line 163) | streamEOF uint32 = 1
constant streamDry (line 164) | streamDry uint32 = 2
constant setBufferLen (line 165) | setBufferLen uint32 = 3
constant streamIsRecorded (line 166) | streamIsRecorded uint32 = 4
constant pingRequest (line 167) | pingRequest uint32 = 6
constant pingResponse (line 168) | pingResponse uint32 = 7
FILE: protocol/rtmp/core/conn_client.go
type ConnClient (line 35) | type ConnClient struct
method DecodeBatch (line 60) | func (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version...
method readRespMsg (line 65) | func (connClient *ConnClient) readRespMsg() error {
method writeMsg (line 134) | func (connClient *ConnClient) writeMsg(args ...interface{}) error {
method writeConnectMsg (line 155) | func (connClient *ConnClient) writeConnectMsg() error {
method writeCreateStreamMsg (line 170) | func (connClient *ConnClient) writeCreateStreamMsg() error {
method writePublishMsg (line 193) | func (connClient *ConnClient) writePublishMsg() error {
method writePlayMsg (line 202) | func (connClient *ConnClient) writePlayMsg() error {
method Start (line 214) | func (connClient *ConnClient) Start(url string, method string) error {
method Write (line 334) | func (connClient *ConnClient) Write(c ChunkStream) error {
method Flush (line 346) | func (connClient *ConnClient) Flush() error {
method Read (line 350) | func (connClient *ConnClient) Read(c *ChunkStream) (err error) {
method GetInfo (line 354) | func (connClient *ConnClient) GetInfo() (app string, name string, url ...
method GetStreamId (line 361) | func (connClient *ConnClient) GetStreamId() uint32 {
method Close (line 365) | func (connClient *ConnClient) Close(err error) {
function NewConnClient (line 51) | func NewConnClient() *ConnClient {
FILE: protocol/rtmp/core/conn_server.go
type ConnectInfo (line 35) | type ConnectInfo struct
type ConnectResp (line 48) | type ConnectResp struct
type ConnectEvent (line 53) | type ConnectEvent struct
type PublishInfo (line 60) | type PublishInfo struct
type ConnServer (line 65) | type ConnServer struct
method writeMsg (line 88) | func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ......
method connect (line 109) | func (connServer *ConnServer) connect(vs []interface{}) error {
method releaseStream (line 138) | func (connServer *ConnServer) releaseStream(vs []interface{}) error {
method fcPublish (line 142) | func (connServer *ConnServer) fcPublish(vs []interface{}) error {
method connectResp (line 146) | func (connServer *ConnServer) connectResp(cur *ChunkStream) error {
method createStream (line 166) | func (connServer *ConnServer) createStream(vs []interface{}) error {
method createStreamResp (line 178) | func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error {
method publishOrPlay (line 182) | func (connServer *ConnServer) publishOrPlay(vs []interface{}) error {
method publishResp (line 201) | func (connServer *ConnServer) publishResp(cur *ChunkStream) error {
method playResp (line 209) | func (connServer *ConnServer) playResp(cur *ChunkStream) error {
method handleCmdMsg (line 244) | func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
method ReadMsg (line 306) | func (connServer *ConnServer) ReadMsg() error {
method IsPublisher (line 325) | func (connServer *ConnServer) IsPublisher() bool {
method Write (line 329) | func (connServer *ConnServer) Write(c ChunkStream) error {
method Flush (line 341) | func (connServer *ConnServer) Flush() error {
method Read (line 345) | func (connServer *ConnServer) Read(c *ChunkStream) (err error) {
method GetInfo (line 349) | func (connServer *ConnServer) GetInfo() (app string, name string, url ...
method Close (line 356) | func (connServer *ConnServer) Close(err error) {
function NewConnServer (line 78) | func NewConnServer(conn *Conn) *ConnServer {
FILE: protocol/rtmp/core/conn_test.go
function TestConnReadNormal (line 13) | func TestConnReadNormal(t *testing.T) {
function TestConnCrossReading (line 42) | func TestConnCrossReading(t *testing.T) {
function TestSetChunksizeForWrite (line 96) | func TestSetChunksizeForWrite(t *testing.T) {
function TestSetChunksize (line 148) | func TestSetChunksize(t *testing.T) {
function TestConnWrite (line 205) | func TestConnWrite(t *testing.T) {
FILE: protocol/rtmp/core/handshake.go
function hsMakeDigest (line 42) | func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) {
function hsCalcDigestPos (line 53) | func hsCalcDigestPos(p []byte, base int) (pos int) {
function hsFindDigest (line 61) | func hsFindDigest(p []byte, key []byte, base int) int {
function hsParse1 (line 70) | func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []b...
function hsCreate01 (line 82) | func hsCreate01(p []byte, time uint32, ver uint32, key []byte) {
function hsCreate2 (line 93) | func hsCreate2(p []byte, key []byte) {
method HandshakeClient (line 100) | func (conn *Conn) HandshakeClient() (err error) {
method HandshakeServer (line 143) | func (conn *Conn) HandshakeServer() (err error) {
FILE: protocol/rtmp/core/read_writer.go
type ReadWriter (line 8) | type ReadWriter struct
method Read (line 20) | func (rw *ReadWriter) Read(p []byte) (int, error) {
method ReadError (line 29) | func (rw *ReadWriter) ReadError() error {
method ReadUintBE (line 33) | func (rw *ReadWriter) ReadUintBE(n int) (uint32, error) {
method ReadUintLE (line 49) | func (rw *ReadWriter) ReadUintLE(n int) (uint32, error) {
method Flush (line 65) | func (rw *ReadWriter) Flush() error {
method Write (line 76) | func (rw *ReadWriter) Write(p []byte) (int, error) {
method WriteError (line 83) | func (rw *ReadWriter) WriteError() error {
method WriteUintBE (line 87) | func (rw *ReadWriter) WriteUintBE(v uint32, n int) error {
method WriteUintLE (line 101) | func (rw *ReadWriter) WriteUintLE(v uint32, n int) error {
function NewReadWriter (line 14) | func NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter {
FILE: protocol/rtmp/core/read_writer_test.go
function TestReader (line 11) | func TestReader(t *testing.T) {
function TestReaderUintBE (line 30) | func TestReaderUintBE(t *testing.T) {
function TestReaderUintLE (line 52) | func TestReaderUintLE(t *testing.T) {
function TestWriter (line 74) | func TestWriter(t *testing.T) {
function TestWriteUintBE (line 90) | func TestWriteUintBE(t *testing.T) {
function TestWriteUintLE (line 114) | func TestWriteUintLE(t *testing.T) {
FILE: protocol/rtmp/rtmp.go
constant maxQueueNum (line 22) | maxQueueNum = 1024
constant SAVE_STATICS_INTERVAL (line 23) | SAVE_STATICS_INTERVAL = 5000
type Client (line 31) | type Client struct
method Dial (line 43) | func (c *Client) Dial(url string, method string) error {
method GetHandle (line 64) | func (c *Client) GetHandle() av.Handler {
function NewRtmpClient (line 36) | func NewRtmpClient(h av.Handler, getter av.GetWriter) *Client {
type Server (line 68) | type Server struct
method Serve (line 80) | func (s *Server) Serve(listener net.Listener) (err error) {
method handleConn (line 100) | func (s *Server) handleConn(conn *core.Conn) error {
function NewRtmpServer (line 73) | func NewRtmpServer(h av.Handler, getter av.GetWriter) *Server {
type GetInFo (line 169) | type GetInFo interface
type StreamReadWriteCloser (line 173) | type StreamReadWriteCloser interface
type StaticsBW (line 180) | type StaticsBW struct
type VirWriter (line 193) | type VirWriter struct
method SaveStatics (line 221) | func (v *VirWriter) SaveStatics(streamid uint32, length uint64, isVide...
method Check (line 245) | func (v *VirWriter) Check() {
method DropPacket (line 255) | func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
method Write (line 287) | func (v *VirWriter) Write(p *av.Packet) (err error) {
method SendPacket (line 308) | func (v *VirWriter) SendPacket() error {
method Info (line 346) | func (v *VirWriter) Info() (ret av.Info) {
method Close (line 359) | func (v *VirWriter) Close(err error) {
function NewVirWriter (line 202) | func NewVirWriter(conn StreamReadWriteCloser) *VirWriter {
type VirReader (line 368) | type VirReader struct
method SaveStatics (line 386) | func (v *VirReader) SaveStatics(streamid uint32, length uint64, isVide...
method Read (line 411) | func (v *VirReader) Read(p *av.Packet) (err error) {
method Info (line 445) | func (v *VirReader) Info() (ret av.Info) {
method Close (line 457) | func (v *VirReader) Close(err error) {
function NewVirReader (line 376) | func NewVirReader(conn StreamReadWriteCloser) *VirReader {
FILE: protocol/rtmp/rtmprelay/rtmprelay.go
type RtmpRelay (line 19) | type RtmpRelay struct
method rcvPlayChunkStream (line 41) | func (self *RtmpRelay) rcvPlayChunkStream() {
method sendPublishChunkStream (line 72) | func (self *RtmpRelay) sendPublishChunkStream() {
method Start (line 88) | func (self *RtmpRelay) Start() error {
method Stop (line 118) | func (self *RtmpRelay) Stop() {
function NewRtmpRelay (line 29) | func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay {
FILE: protocol/rtmp/rtmprelay/staticrelay.go
type StaticPush (line 14) | type StaticPush struct
method Start (line 103) | func (self *StaticPush) Start() error {
method Stop (line 123) | func (self *StaticPush) Stop() {
method WriteAvPacket (line 133) | func (self *StaticPush) WriteAvPacket(packet *av.Packet) {
method sendPacket (line 141) | func (self *StaticPush) sendPacket(p *av.Packet) {
method HandleAvPacket (line 168) | func (self *StaticPush) HandleAvPacket() {
method IsStart (line 188) | func (self *StaticPush) IsStart() bool {
function GetStaticPushList (line 30) | func GetStaticPushList(appname string) ([]string, error) {
function GetAndCreateStaticPushObject (line 48) | func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush {
function GetStaticPushObject (line 67) | func GetStaticPushObject(rtmpurl string) (*StaticPush, error) {
function ReleaseStaticPushObject (line 78) | func ReleaseStaticPushObject(rtmpurl string) {
function NewStaticPush (line 93) | func NewStaticPush(rtmpurl string) *StaticPush {
FILE: protocol/rtmp/stream.go
type RtmpStream (line 20) | type RtmpStream struct
method HandleReader (line 32) | func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
method HandleWriter (line 56) | func (rs *RtmpStream) HandleWriter(w av.WriteCloser) {
method GetStreams (line 73) | func (rs *RtmpStream) GetStreams() *sync.Map {
method CheckAlive (line 77) | func (rs *RtmpStream) CheckAlive() {
function NewRtmpStream (line 24) | func NewRtmpStream() *RtmpStream {
type Stream (line 90) | type Stream struct
method ID (line 114) | func (s *Stream) ID() string {
method GetReader (line 121) | func (s *Stream) GetReader() av.ReadCloser {
method GetWs (line 125) | func (s *Stream) GetWs() *sync.Map {
method Copy (line 129) | func (s *Stream) Copy(dst *Stream) {
method AddReader (line 140) | func (s *Stream) AddReader(r av.ReadCloser) {
method AddWriter (line 145) | func (s *Stream) AddWriter(w av.WriteCloser) {
method StartStaticPush (line 153) | func (s *Stream) StartStaticPush() {
method StopStaticPush (line 193) | func (s *Stream) StopStaticPush() {
method IsSendStaticPush (line 232) | func (s *Stream) IsSendStaticPush() bool {
method SendStaticPush (line 272) | func (s *Stream) SendStaticPush(packet av.Packet) {
method TransStart (line 309) | func (s *Stream) TransStart() {
method TransStop (line 359) | func (s *Stream) TransStop() {
method CheckAlive (line 369) | func (s *Stream) CheckAlive() (n int) {
method closeInter (line 396) | func (s *Stream) closeInter() {
type PackWriterCloser (line 98) | type PackWriterCloser struct
method GetWriter (line 103) | func (p *PackWriterCloser) GetWriter() av.WriteCloser {
function NewStream (line 107) | func NewStream() *Stream {
FILE: utils/pio/reader.go
function U8 (line 3) | func U8(b []byte) (i uint8) {
function U16BE (line 7) | func U16BE(b []byte) (i uint16) {
function I16BE (line 14) | func I16BE(b []byte) (i int16) {
function I24BE (line 21) | func I24BE(b []byte) (i int32) {
function U24BE (line 30) | func U24BE(b []byte) (i uint32) {
function I32BE (line 39) | func I32BE(b []byte) (i int32) {
function U32LE (line 50) | func U32LE(b []byte) (i uint32) {
function U32BE (line 61) | func U32BE(b []byte) (i uint32) {
function U40BE (line 72) | func U40BE(b []byte) (i uint64) {
function U64BE (line 85) | func U64BE(b []byte) (i uint64) {
function I64BE (line 104) | func I64BE(b []byte) (i int64) {
FILE: utils/pio/writer.go
function PutU8 (line 3) | func PutU8(b []byte, v uint8) {
function PutI16BE (line 7) | func PutI16BE(b []byte, v int16) {
function PutU16BE (line 12) | func PutU16BE(b []byte, v uint16) {
function PutI24BE (line 17) | func PutI24BE(b []byte, v int32) {
function PutU24BE (line 23) | func PutU24BE(b []byte, v uint32) {
function PutI32BE (line 29) | func PutI32BE(b []byte, v int32) {
function PutU32BE (line 36) | func PutU32BE(b []byte, v uint32) {
function PutU32LE (line 43) | func PutU32LE(b []byte, v uint32) {
function PutU40BE (line 50) | func PutU40BE(b []byte, v uint64) {
function PutU48BE (line 58) | func PutU48BE(b []byte, v uint64) {
function PutU64BE (line 67) | func PutU64BE(b []byte, v uint64) {
function PutI64BE (line 78) | func PutI64BE(b []byte, v int64) {
FILE: utils/pool/pool.go
type Pool (line 3) | type Pool struct
method Get (line 10) | func (pool *Pool) Get(size int) []byte {
constant maxpoolsize (line 8) | maxpoolsize = 500 * 1024
function NewPool (line 20) | func NewPool() *Pool {
FILE: utils/queue/queue.go
type Queue (line 10) | type Queue struct
method Push (line 26) | func (q *Queue) Push(msg *av.Packet) {
method Pop (line 38) | func (q *Queue) Pop() *av.Packet {
method pop (line 49) | func (q *Queue) pop() *av.Packet {
method Len (line 57) | func (q *Queue) Len() int {
method All (line 65) | func (q *Queue) All() []*av.Packet {
function NewQueue (line 19) | func NewQueue(maxSize int) *Queue {
FILE: utils/uid/rand.go
function RandStringRunes (line 7) | func RandStringRunes(n int) string {
FILE: utils/uid/uuid.go
function NewId (line 9) | func NewId() string {
Condensed preview — 79 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (310K chars).
[
{
"path": ".github/workflows/codeql.yml",
"chars": 4300,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/release.yml",
"chars": 721,
"preview": "name: Release\non:\n release:\n types: [published]\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n env:\n GITHUB"
},
{
"path": ".github/workflows/test.yml",
"chars": 474,
"preview": "name: Test\non: [push]\njobs:\n test:\n name: Build\n runs-on: ${{ matrix.os }}\n strategy:\n matrix:\n os"
},
{
"path": ".gitignore",
"chars": 95,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n.idea\ndist\n.vscode\ntmp\nvendor\nlivego\nlivego.exe\n"
},
{
"path": ".goreleaser.yml",
"chars": 217,
"preview": "before:\n hooks:\n - go mod tidy\nbuilds:\n - binary: livego\n id: livego\n main: ./main.go\n goos:\n - windo"
},
{
"path": "CHANGELOG.md",
"chars": 1404,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "Dockerfile",
"chars": 495,
"preview": "FROM golang:latest as builder\nWORKDIR /app\nENV GOPROXY https://goproxy.io\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY"
},
{
"path": "LICENSE",
"chars": 1060,
"preview": "MIT License\n\nCopyright (c) 2017 吴浩麟\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
},
{
"path": "Makefile",
"chars": 705,
"preview": "GOCMD ?= go\nGOBUILD = $(GOCMD) build\nGOCLEAN = $(GOCMD) clean\nGOTEST = $(GOCMD) test\nGOGET = $(GOCMD) get\nBINARY_NAME = "
},
{
"path": "README.md",
"chars": 3413,
"preview": "<p align='center'>\n <img src='./logo.png' width='200px' height='80px'/>\n</p>\n\n[中文](./README_cn.md)\n\n[\n\nconst (\n\tTAG_AUDIO = 8\n\tTAG_VIDEO = 9\n\tTAG_SCRIPTDATAAMF0 = 18\n\tT"
},
{
"path": "av/rwbase.go",
"chars": 1048,
"preview": "package av\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype RWBaser struct {\n\tlock sync.Mutex\n\ttimeout time.Du"
},
{
"path": "configure/channel.go",
"chars": 2716,
"preview": "package configure\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n\n\t\"github.com/go-redis/redis/v7\"\n\t\"github.c"
},
{
"path": "configure/liveconfig.go",
"chars": 5186,
"preview": "package configure\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/kr/pretty\"\n\tlog \"github.com/sirupsen/logr"
},
{
"path": "container/flv/demuxer.go",
"chars": 683,
"preview": "package flv\n\nimport (\n\t\"fmt\"\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nvar (\n\tErrAvcEndSEQ = fmt.Errorf(\"avc end sequence\")\n)"
},
{
"path": "container/flv/muxer.go",
"chars": 3617,
"preview": "package flv\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/"
},
{
"path": "container/flv/tag.go",
"chars": 3622,
"preview": "package flv\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\ntype flvTag struct {\n\tfType uint8\n\tdataSize uint"
},
{
"path": "container/ts/crc32.go",
"chars": 3464,
"preview": "package ts\n\nfunc GenCrc32(src []byte) uint32 {\n\tcrcTable := []uint32{\n\t\t0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,\n"
},
{
"path": "container/ts/muxer.go",
"chars": 7053,
"preview": "package ts\n\nimport (\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nconst (\n\ttsDefaultDataLen = 184\n\ttsPacketLen = 188"
},
{
"path": "container/ts/muxer_test.go",
"chars": 1940,
"preview": "package ts\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype TestWr"
},
{
"path": "go.mod",
"chars": 550,
"preview": "module github.com/gwuhaolin/livego\n\ngo 1.13\n\nrequire (\n\tgithub.com/auth0/go-jwt-middleware v0.0.0-20190805220309-3608124"
},
{
"path": "go.sum",
"chars": 19008,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1 h1:"
},
{
"path": "livego.yaml",
"chars": 449,
"preview": "# # Logger level\n# level: info\n\n# # FLV Options\n# flv_archive: false\n# flv_dir: \"./tmp\"\n# httpflv_addr: \":7001\"\n\n# # RTM"
},
{
"path": "main.go",
"chars": 3830,
"preview": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"path\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/configure\"\n"
},
{
"path": "parser/aac/parser.go",
"chars": 2636,
"preview": "package aac\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\ntype mpegExtension struct {\n\tobjectType byte\n\ts"
},
{
"path": "parser/h264/parser.go",
"chars": 5532,
"preview": "package h264\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n)\n\nconst (\n\ti_frame byte = 0\n\tp_frame byte = 1\n\tb_frame byte = 2\n)\n\nconst ("
},
{
"path": "parser/h264/parser_test.go",
"chars": 2874,
"preview": "package h264\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestH264SeqDemux(t *tes"
},
{
"path": "parser/mp3/parser.go",
"chars": 842,
"preview": "package mp3\n\nimport (\n\t\"fmt\"\n)\n\ntype Parser struct {\n\tsamplingFrequency int\n}\n\nfunc NewParser() *Parser {\n\treturn &Parse"
},
{
"path": "parser/parser.go",
"chars": 1412,
"preview": "package parser\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/parser/aac\"\n\t\"git"
},
{
"path": "protocol/amf/amf.go",
"chars": 938,
"preview": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nfunc (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err er"
},
{
"path": "protocol/amf/amf_test.go",
"chars": 4562,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc EncodeAndDecode(val interface{}, ver Version"
},
{
"path": "protocol/amf/const.go",
"chars": 2132,
"preview": "package amf\n\nimport (\n\t\"io\"\n)\n\nconst (\n\tAMF0 = 0x00\n\tAMF3 = 0x03\n)\n\nconst (\n\tAMF0_NUMBER_MARKER = 0x00\n\tAMF0_BOO"
},
{
"path": "protocol/amf/decoder_amf0.go",
"chars": 9333,
"preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// amf0 polymorphic router\nfunc (d *Decoder) DecodeAmf0(r io.Re"
},
{
"path": "protocol/amf/decoder_amf0_test.go",
"chars": 13189,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestDecodeAmf0Number(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x"
},
{
"path": "protocol/amf/decoder_amf3.go",
"chars": 12538,
"preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// amf3 polymorphic router\nfunc (d *Decoder) DecodeAmf3"
},
{
"path": "protocol/amf/decoder_amf3_external.go",
"chars": 3162,
"preview": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n)\n\n// Abstract external boilerplate\nfunc (d *Decoder) decodeAbstractMessage(r"
},
{
"path": "protocol/amf/decoder_amf3_test.go",
"chars": 4419,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype u29TestCase struct {\n\tvalue uint32\n\texpect []byte\n}\n\nvar u29TestCases"
},
{
"path": "protocol/amf/encoder_amf0.go",
"chars": 7168,
"preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n)\n\n// amf0 polymorphic router\nfunc (e *Encoder) EncodeA"
},
{
"path": "protocol/amf/encoder_amf0_test.go",
"chars": 4728,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf0Number(t *testing.T) {\n\tbuf := new(by"
},
{
"path": "protocol/amf/encoder_amf3.go",
"chars": 9578,
"preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"sort\"\n\t\"time\"\n)\n\n// amf3 polymorphic router\n\nfunc (e "
},
{
"path": "protocol/amf/encoder_amf3_test.go",
"chars": 4233,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf3EmptyString(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf :"
},
{
"path": "protocol/amf/metadata.go",
"chars": 1239,
"preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tADD = 0x0\n\tDEL = 0x3\n)\n\nconst (\n\tSe"
},
{
"path": "protocol/amf/util.go",
"chars": 1536,
"preview": "package amf\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\nfunc DumpBytes(label string, buf []byte, size int) {\n\tfmt.Printf("
},
{
"path": "protocol/api/api.go",
"chars": 12265,
"preview": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaol"
},
{
"path": "protocol/hls/align.go",
"chars": 461,
"preview": "package hls\n\nconst (\n\tsyncms = 2 // ms\n)\n\ntype align struct {\n\tframeNum uint64\n\tframeBase uint64\n}\n\nfunc (a *align) ali"
},
{
"path": "protocol/hls/audio_cache.go",
"chars": 701,
"preview": "package hls\n\nimport \"bytes\"\n\nconst (\n\tcache_max_frames byte = 6\n\taudio_cache_len int = 10 * 1024\n)\n\ntype audioCache st"
},
{
"path": "protocol/hls/cache.go",
"chars": 1705,
"preview": "package hls\n\nimport (\n\t\"bytes\"\n\t\"container/list\"\n\t\"fmt\"\n\t\"sync\"\n)\n\nconst (\n\tmaxTSCacheNum = 3\n)\n\nvar (\n\tErrNoKey = fmt.E"
},
{
"path": "protocol/hls/hls.go",
"chars": 3934,
"preview": "package hls\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/li"
},
{
"path": "protocol/hls/item.go",
"chars": 320,
"preview": "package hls\n\ntype TSItem struct {\n\tName string\n\tSeqNum int\n\tDuration int\n\tData []byte\n}\n\nfunc NewTSItem(name s"
},
{
"path": "protocol/hls/source.go",
"chars": 6512,
"preview": "package hls\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av"
},
{
"path": "protocol/hls/status.go",
"chars": 759,
"preview": "package hls\n\nimport \"time\"\n\ntype status struct {\n\thasVideo bool\n\tseqId int64\n\tcreatedAt time.Time\n\ts"
},
{
"path": "protocol/httpflv/server.go",
"chars": 3118,
"preview": "package httpflv\n\nimport (\n\t\"encoding/json\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com"
},
{
"path": "protocol/httpflv/writer.go",
"chars": 4209,
"preview": "package httpflv\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/pr"
},
{
"path": "protocol/rtmp/cache/cache.go",
"chars": 1278,
"preview": "package cache\n\nimport (\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n)\n\ntype Cache struct "
},
{
"path": "protocol/rtmp/cache/gop.go",
"chars": 2178,
"preview": "package cache\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nvar (\n\tmaxGOPCap int = 1024\n\tErrGopTooBig = "
},
{
"path": "protocol/rtmp/cache/special.go",
"chars": 905,
"preview": "package cache\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/protocol/amf\"\n\n\tlog \"g"
},
{
"path": "protocol/rtmp/core/chunk_stream.go",
"chars": 5283,
"preview": "package core\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/utils/"
},
{
"path": "protocol/rtmp/core/chunk_stream_test.go",
"chars": 2322,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/utils/pool\"\n\n\t\"github.com/stretchr/testify/ass"
},
{
"path": "protocol/rtmp/core/conn.go",
"chars": 4492,
"preview": "package core\n\nimport (\n\t\"encoding/binary\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/utils/pio\"\n\t\"github.com/gwuhaoli"
},
{
"path": "protocol/rtmp/core/conn_client.go",
"chars": 8271,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\tneturl \"net/url\"\n\t\"string"
},
{
"path": "protocol/rtmp/core/conn_server.go",
"chars": 8958,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/protocol/a"
},
{
"path": "protocol/rtmp/core/conn_test.go",
"chars": 6081,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/utils/pool\"\n\n\t\"github.com/stretchr/testi"
},
{
"path": "protocol/rtmp/core/handshake.go",
"chars": 4584,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"time\"\n\n\t\"github.com/gwuha"
},
{
"path": "protocol/rtmp/core/read_writer.go",
"chars": 2122,
"preview": "package core\n\nimport (\n\t\"bufio\"\n\t\"io\"\n)\n\ntype ReadWriter struct {\n\t*bufio.ReadWriter\n\treadError error\n\twriteError error"
},
{
"path": "protocol/rtmp/core/read_writer_test.go",
"chars": 3095,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestReader(t *testing.T)"
},
{
"path": "protocol/rtmp/rtmp.go",
"chars": 11177,
"preview": "package rtmp\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n"
},
{
"path": "protocol/rtmp/rtmprelay/rtmprelay.go",
"chars": 3301,
"preview": "package rtmprelay\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/proto"
},
{
"path": "protocol/rtmp/rtmprelay/staticrelay.go",
"chars": 4241,
"preview": "package rtmprelay\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t"
},
{
"path": "protocol/rtmp/stream.go",
"chars": 8745,
"preview": "package rtmp\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/liveg"
},
{
"path": "test.go",
"chars": 13,
"preview": "package main\n"
},
{
"path": "utils/pio/pio.go",
"chars": 48,
"preview": "package pio\n\nvar RecommendBufioSize = 1024 * 64\n"
},
{
"path": "utils/pio/reader.go",
"chars": 1609,
"preview": "package pio\n\nfunc U8(b []byte) (i uint8) {\n\treturn b[0]\n}\n\nfunc U16BE(b []byte) (i uint16) {\n\ti = uint16(b[0])\n\ti <<= 8\n"
},
{
"path": "utils/pio/writer.go",
"chars": 1483,
"preview": "package pio\n\nfunc PutU8(b []byte, v uint8) {\n\tb[0] = v\n}\n\nfunc PutI16BE(b []byte, v int16) {\n\tb[0] = byte(v >> 8)\n\tb[1] "
},
{
"path": "utils/pool/pool.go",
"chars": 370,
"preview": "package pool\n\ntype Pool struct {\n\tpos int\n\tbuf []byte\n}\n\nconst maxpoolsize = 500 * 1024\n\nfunc (pool *Pool) Get(size int)"
},
{
"path": "utils/queue/queue.go",
"chars": 1218,
"preview": "package queue\n\nimport (\n\t\"sync\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\n// Queue is a basic FIFO queue for Messages.\ntype "
},
{
"path": "utils/uid/rand.go",
"chars": 277,
"preview": "package uid\n\nimport \"math/rand\"\n\nvar letterRunes = []rune(\"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY"
},
{
"path": "utils/uid/uuid.go",
"chars": 189,
"preview": "package uid\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc NewId() string {\n\tid := uuid.NewV4()\n\tb64"
}
]
About this extraction
This page contains the full source code of the gwuhaolin/livego GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 79 files (272.9 KB), approximately 99.1k tokens, and a symbol index with 690 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.