first
This commit is contained in:
commit
fc3c9d7af3
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.*.swp
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
Rtmp
|
||||
|
||||
Golang rtmp server
|
||||
|
||||
Run a simple server
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/go-av/rtmp"
|
||||
|
||||
func main() {
|
||||
rtmp.SimpleServer()
|
||||
}
|
||||
|
||||
Use avconv to publish stream
|
||||
|
||||
avconv -re -i a.mp4 -c:a copy -c:v copy -f flv rtmp://localhost/myapp/1
|
||||
|
||||
Use avplay to play stream
|
||||
|
||||
avplay rtmp://localhost/myapp/1
|
||||
|
100
amf.go
Normal file
100
amf.go
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
var (
|
||||
AMF_NUMBER = 0x00
|
||||
AMF_BOOLEAN = 0x01
|
||||
AMF_STRING = 0x02
|
||||
AMF_OBJECT = 0x03
|
||||
AMF_NULL = 0x05
|
||||
AMF_ARRAY_NULL = 0x06
|
||||
AMF_MIXED_ARRAY = 0x08
|
||||
AMF_END = 0x09
|
||||
AMF_ARRAY = 0x0a
|
||||
|
||||
AMF_INT8 = 0x0100
|
||||
AMF_INT16 = 0x0101
|
||||
AMF_INT32 = 0x0102
|
||||
AMF_VARIANT_ = 0x0103
|
||||
)
|
||||
|
||||
type AMFObj struct {
|
||||
atype int
|
||||
str string
|
||||
i int
|
||||
buf []byte
|
||||
obj map[string]AMFObj
|
||||
f64 float64
|
||||
}
|
||||
|
||||
func ReadAMF(r io.Reader) (a AMFObj) {
|
||||
a.atype = ReadInt(r, 1)
|
||||
switch (a.atype) {
|
||||
case AMF_STRING:
|
||||
n := ReadInt(r, 2)
|
||||
b := ReadBuf(r, n)
|
||||
a.str = string(b)
|
||||
case AMF_NUMBER:
|
||||
binary.Read(r, binary.BigEndian, &a.f64)
|
||||
case AMF_BOOLEAN:
|
||||
a.i = ReadInt(r, 1)
|
||||
case AMF_MIXED_ARRAY:
|
||||
ReadInt(r, 4)
|
||||
fallthrough
|
||||
case AMF_OBJECT:
|
||||
a.obj = map[string]AMFObj{}
|
||||
for {
|
||||
n := ReadInt(r, 2)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
name := string(ReadBuf(r, n))
|
||||
a.obj[name] = ReadAMF(r)
|
||||
}
|
||||
case AMF_ARRAY, AMF_VARIANT_:
|
||||
panic("amf: read: unsupported array or variant")
|
||||
case AMF_INT8:
|
||||
a.i = ReadInt(r, 1)
|
||||
case AMF_INT16:
|
||||
a.i = ReadInt(r, 2)
|
||||
case AMF_INT32:
|
||||
a.i = ReadInt(r, 4)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func WriteAMF(r io.Writer, a AMFObj) {
|
||||
WriteInt(r, a.atype, 1)
|
||||
switch (a.atype) {
|
||||
case AMF_STRING:
|
||||
WriteInt(r, len(a.str), 2)
|
||||
r.Write([]byte(a.str))
|
||||
case AMF_NUMBER:
|
||||
binary.Write(r, binary.BigEndian, a.f64)
|
||||
case AMF_BOOLEAN:
|
||||
WriteInt(r, a.i, 1)
|
||||
case AMF_MIXED_ARRAY:
|
||||
r.Write(a.buf[:4])
|
||||
case AMF_OBJECT:
|
||||
for name, val := range a.obj {
|
||||
WriteInt(r, len(name), 2)
|
||||
r.Write([]byte(name))
|
||||
WriteAMF(r, val)
|
||||
}
|
||||
WriteInt(r, 9, 3)
|
||||
case AMF_ARRAY, AMF_VARIANT_:
|
||||
panic("amf: write unsupported array, var")
|
||||
case AMF_INT8:
|
||||
WriteInt(r, a.i, 1)
|
||||
case AMF_INT16:
|
||||
WriteInt(r, a.i, 2)
|
||||
case AMF_INT32:
|
||||
WriteInt(r, a.i, 4)
|
||||
}
|
||||
}
|
||||
|
21
amf_test.go
Normal file
21
amf_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"encoding/base64"
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
data = `AgAHY29ubmVjdAA/8AAAAAAAAAMAA2FwcAIABW15YXBwAAhmbGFzaFZlcgIAEE1BQyAxMSw1LDUwMiwxNDkABnN3ZlVybAIAJmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MS9zd2YvandwbGF5ZXIuc3dmAAV0Y1VybAIAFnJ0bXA6Ly9sb2NhbGhvc3QvbXlhcHAABGZwYWQBAAAMY2FwYWJpbGl0aWVzAEBt4AAAAAAAAAthdWRpb0NvZGVjcwBAq+4AAAAAAAALdmlkZW9Db2RlY3MAQG+AAAAAAAAADXZpZGVvRnVuY3Rpb24AP/AAAAAAAAAAB3BhZ2VVcmwCABpodHRwOi8vbG9jYWxob3N0OjgwODEvc3dmLwAOb2JqZWN0RW5jb2RpbmcAAAAAAAAAAAAAAAk=`
|
||||
)
|
||||
|
||||
func TestHal(t *testing.T) {
|
||||
dec := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(data))
|
||||
r := NewAMFReader(dec)
|
||||
obj := r.ReadAMF()
|
||||
fmt.Printf("%v\n", obj)
|
||||
}
|
||||
|
145
handshake.go
Normal file
145
handshake.go
Normal file
@ -0,0 +1,145 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"bytes"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
var (
|
||||
clientKey = []byte{
|
||||
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
|
||||
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
|
||||
'0', '0', '1',
|
||||
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
|
||||
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,
|
||||
}
|
||||
serverKey = []byte{
|
||||
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
|
||||
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
|
||||
'S', 'e', 'r', 'v', 'e', 'r', ' ',
|
||||
'0', '0', '1',
|
||||
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
|
||||
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,
|
||||
}
|
||||
clientKey2 = clientKey[:30]
|
||||
serverKey2 = serverKey[:36]
|
||||
serverVersion = []byte{
|
||||
0x0D, 0x0E, 0x0A, 0x0D,
|
||||
}
|
||||
)
|
||||
|
||||
func makeDigest(key []byte, src []byte, skip int) (dst []byte) {
|
||||
h := hmac.New(sha256.New, key)
|
||||
if skip >= 0 && skip < len(src) {
|
||||
if skip != 0 {
|
||||
h.Write(src[:skip])
|
||||
}
|
||||
if len(src) != skip + 32 {
|
||||
h.Write(src[skip+32:])
|
||||
}
|
||||
} else {
|
||||
h.Write(src)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func findDigest(b []byte, key []byte, base int) (int) {
|
||||
offs := 0
|
||||
for n := 0; n < 4; n++ {
|
||||
offs += int(b[base + n])
|
||||
}
|
||||
offs = (offs % 728) + base + 4
|
||||
// fmt.Printf("offs %v\n", offs)
|
||||
dig := makeDigest(key, b, offs)
|
||||
// fmt.Printf("digest %v\n", digest)
|
||||
// fmt.Printf("p %v\n", b[offs:offs+32])
|
||||
if bytes.Compare(b[offs:offs+32], dig) != 0 {
|
||||
offs = -1
|
||||
}
|
||||
return offs
|
||||
}
|
||||
|
||||
func writeDigest(b []byte, key []byte, base int) {
|
||||
offs := 0
|
||||
for n := 8; n < 12; n++ {
|
||||
offs += int(b[base + n])
|
||||
}
|
||||
offs = (offs % 728) + base + 12
|
||||
|
||||
dig := makeDigest(key, b, offs)
|
||||
copy(b[offs:], dig)
|
||||
}
|
||||
|
||||
func createChal(b []byte, ver []byte, key []byte) {
|
||||
b[0] = 3
|
||||
copy(b[5:9], ver)
|
||||
for i := 9; i < 1537; i++ {
|
||||
b[i] = byte(rand.Int() % 256)
|
||||
}
|
||||
writeDigest(b[1:], key, 0)
|
||||
}
|
||||
|
||||
func createResp(b []byte, key []byte) {
|
||||
for i := 0; i < 1536; i++ {
|
||||
b[i] = byte(rand.Int() % 256)
|
||||
}
|
||||
dig := makeDigest(key, b, 1536-32)
|
||||
copy(b[1536-32:], dig)
|
||||
}
|
||||
|
||||
func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) {
|
||||
if b[0] != 0x3 {
|
||||
l.Printf("handshake: invalid rtmp version\n")
|
||||
err = 1
|
||||
return
|
||||
}
|
||||
|
||||
epoch := b[1:5]
|
||||
ver := b[5:9]
|
||||
l.Printf("handshake: epoch %v ver %v\n", epoch, ver)
|
||||
|
||||
var offs int
|
||||
if offs = findDigest(b[1:], peerKey, 772); offs == -1 {
|
||||
if offs = findDigest(b[1:], peerKey, 8); offs == -1 {
|
||||
l.Printf("handshake: digest not found\n")
|
||||
err = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
l.Printf("handshake: offs = %v\n", offs)
|
||||
|
||||
dig = makeDigest(key, b[1+offs:1+offs+32], -1)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func handShake(rw io.ReadWriter) {
|
||||
b := ReadBuf(rw, 1537)
|
||||
l.Printf("handshake: got client chal\n")
|
||||
dig, err := parseChal(b, clientKey2, serverKey)
|
||||
if err != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
createChal(b, serverVersion, serverKey2)
|
||||
l.Printf("handshake: send server chal\n")
|
||||
rw.Write(b)
|
||||
|
||||
b = make([]byte, 1536)
|
||||
createResp(b, dig)
|
||||
l.Printf("handshake: send server resp\n")
|
||||
rw.Write(b)
|
||||
|
||||
b = ReadBuf(rw, 1536)
|
||||
l.Printf("handshake: got client resp\n")
|
||||
}
|
||||
|
299
msg.go
Normal file
299
msg.go
Normal file
@ -0,0 +1,299 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
MSG_CHUNK_SIZE = 1
|
||||
MSG_ABORT = 2
|
||||
MSG_ACK = 3
|
||||
MSG_USER = 4
|
||||
MSG_ACK_SIZE = 5
|
||||
MSG_BANDWIDTH = 6
|
||||
MSG_EDGE = 7
|
||||
MSG_AUDIO = 8
|
||||
MSG_VIDEO = 9
|
||||
MSG_AMF3_META = 15
|
||||
MSG_AMF3_SHARED = 16
|
||||
MSG_AMF3_CMD = 17
|
||||
MSG_AMF_META = 18
|
||||
MSG_AMF_SHARED = 19
|
||||
MSG_AMF_CMD = 20
|
||||
MSG_AGGREGATE = 22
|
||||
MSG_MAX = 22
|
||||
)
|
||||
|
||||
var (
|
||||
MsgTypeStr = []string {
|
||||
"?",
|
||||
"CHUNK_SIZE", "ABORT", "ACK",
|
||||
"USER", "ACK_SIZE", "BANDWIDTH", "EDGE",
|
||||
"AUDIO", "VIDEO",
|
||||
"AMF3_META", "AMF3_SHARED", "AFM3_CMD",
|
||||
"AMF_META", "AMF_SHARED", "AMF_CMD",
|
||||
"AGGREGATE",
|
||||
}
|
||||
)
|
||||
|
||||
type chunkHeader struct {
|
||||
typeid int
|
||||
mlen int
|
||||
csid int
|
||||
cfmt int
|
||||
ts int
|
||||
tsdelta int
|
||||
strid int
|
||||
}
|
||||
|
||||
func readChunkHeader (r io.Reader) (m chunkHeader) {
|
||||
i := ReadInt(r, 1)
|
||||
m.cfmt = (i>>6)&3;
|
||||
m.csid = i&0x3f;
|
||||
|
||||
if m.csid == 0 {
|
||||
j := ReadInt(r, 1)
|
||||
m.csid = j + 64
|
||||
}
|
||||
|
||||
if m.csid == 0x3f {
|
||||
j := ReadInt(r, 2)
|
||||
m.csid = j + 64
|
||||
}
|
||||
|
||||
if m.cfmt == 0 {
|
||||
m.ts = ReadInt(r, 3)
|
||||
m.mlen = ReadInt(r, 3)
|
||||
m.typeid = ReadInt(r, 1)
|
||||
m.strid = ReadIntLE(r, 4)
|
||||
}
|
||||
|
||||
if m.cfmt == 1 {
|
||||
m.tsdelta = ReadInt(r, 3)
|
||||
m.mlen = ReadInt(r, 3)
|
||||
m.typeid = ReadInt(r, 1)
|
||||
}
|
||||
|
||||
if m.cfmt == 2 {
|
||||
m.tsdelta = ReadInt(r, 3)
|
||||
}
|
||||
|
||||
if m.ts == 0xffffff {
|
||||
m.ts = ReadInt(r, 4)
|
||||
}
|
||||
if m.tsdelta == 0xffffff {
|
||||
m.tsdelta = ReadInt(r, 4)
|
||||
}
|
||||
|
||||
//l.Printf("chunk: %v", m)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
UNKNOWN = 0
|
||||
PLAYER = 1
|
||||
PUBLISHER = 2
|
||||
)
|
||||
|
||||
const (
|
||||
WAIT_EXTRA = 0
|
||||
WAIT_DATA = 1
|
||||
)
|
||||
|
||||
|
||||
type MsgStream struct {
|
||||
r stream
|
||||
Msg map[int]*Msg
|
||||
vts, ats int
|
||||
|
||||
id string
|
||||
role int
|
||||
stat int
|
||||
app string
|
||||
W,H int
|
||||
strid int
|
||||
extraA, extraV []byte
|
||||
que chan *Msg
|
||||
l *log.Logger
|
||||
}
|
||||
|
||||
type Msg struct {
|
||||
chunkHeader
|
||||
data *bytes.Buffer
|
||||
|
||||
key bool
|
||||
curts int
|
||||
}
|
||||
|
||||
func (m *Msg) String() string {
|
||||
var typestr string
|
||||
if m.typeid < len(MsgTypeStr) {
|
||||
typestr = MsgTypeStr[m.typeid]
|
||||
} else {
|
||||
typestr = "?"
|
||||
}
|
||||
return fmt.Sprintf("%s %d %v", typestr, m.mlen, m.chunkHeader)
|
||||
}
|
||||
|
||||
var (
|
||||
mrseq = 0
|
||||
)
|
||||
|
||||
func NewMsgStream(r io.ReadWriteCloser) *MsgStream {
|
||||
mrseq++
|
||||
return &MsgStream{
|
||||
r:stream{r},
|
||||
Msg:map[int]*Msg{},
|
||||
id:fmt.Sprintf("#%d", mrseq),
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MsgStream) String() string {
|
||||
return mr.id
|
||||
}
|
||||
|
||||
func (mr *MsgStream) Close() {
|
||||
mr.r.Close()
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteMsg(cfmt, csid, typeid, strid, ts int, data []byte) {
|
||||
var b bytes.Buffer
|
||||
start := 0
|
||||
for i := 0; start < len(data); i++ {
|
||||
if i == 0 {
|
||||
if cfmt == 0 {
|
||||
WriteInt(&b, csid, 1) // fmt=0 csid
|
||||
WriteInt(&b, ts, 3) // ts
|
||||
WriteInt(&b, len(data), 3) // message length
|
||||
WriteInt(&b, typeid, 1) // message type id
|
||||
WriteIntLE(&b, strid, 4) // message stream id
|
||||
} else {
|
||||
WriteInt(&b, 0x1<<6 + csid, 1) // fmt=1 csid
|
||||
WriteInt(&b, ts, 3) // tsdelta
|
||||
WriteInt(&b, len(data), 3) // message length
|
||||
WriteInt(&b, typeid, 1) // message type id
|
||||
}
|
||||
} else {
|
||||
WriteBuf(&b, []byte{0x3<<6 + byte(csid)}) // fmt=3, csid
|
||||
}
|
||||
size := 128
|
||||
if len(data) - start < size {
|
||||
size = len(data) - start
|
||||
}
|
||||
WriteBuf(&b, data[start:start+size])
|
||||
WriteBuf(r.r, b.Bytes())
|
||||
b.Reset()
|
||||
start += size
|
||||
}
|
||||
l.Printf("Msg: csid %d ts %d paylen %d", csid, ts, len(data))
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteAudio(strid, ts int, data []byte) {
|
||||
d := append([]byte{0xaf, 1}, data...)
|
||||
tsdelta := ts - r.ats
|
||||
r.ats = ts
|
||||
r.WriteMsg(1, 7, MSG_AUDIO, strid, tsdelta, d)
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteAAC(strid, ts int, data []byte) {
|
||||
d := append([]byte{0xaf, 0}, data...)
|
||||
r.ats = ts
|
||||
r.WriteMsg(0, 7, MSG_AUDIO, strid, ts, d)
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteVideo(strid,ts int, key bool, data []byte) {
|
||||
var b int
|
||||
if key {
|
||||
b = 0x17
|
||||
} else {
|
||||
b = 0x27
|
||||
}
|
||||
d := append([]byte{byte(b), 1, 0, 0, 0x50}, data...)
|
||||
tsdelta := ts - r.vts
|
||||
r.vts = ts
|
||||
r.WriteMsg(1, 6, MSG_VIDEO, strid, tsdelta, d)
|
||||
}
|
||||
|
||||
func (r *MsgStream) WritePPS(strid, ts int, data []byte) {
|
||||
d := append([]byte{0x17, 0, 0, 0, 0}, data...)
|
||||
r.vts = ts
|
||||
r.WriteMsg(0, 6, MSG_VIDEO, strid, ts, d)
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteAMFMeta(csid, strid int, a []AMFObj) {
|
||||
var b bytes.Buffer
|
||||
for _, v := range a {
|
||||
WriteAMF(&b, v)
|
||||
}
|
||||
r.WriteMsg(0, csid, MSG_AMF_META, strid, 0, b.Bytes())
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteAMFCmd(csid, strid int, a []AMFObj) {
|
||||
var b bytes.Buffer
|
||||
for _, v := range a {
|
||||
WriteAMF(&b, v)
|
||||
}
|
||||
r.WriteMsg(0, csid, MSG_AMF_CMD, strid, 0, b.Bytes())
|
||||
}
|
||||
|
||||
func (r *MsgStream) WriteMsg32(csid, typeid, strid, v int) {
|
||||
var b bytes.Buffer
|
||||
WriteInt(&b, v, 4)
|
||||
r.WriteMsg(0, csid, typeid, strid, 0, b.Bytes())
|
||||
}
|
||||
|
||||
func (r *MsgStream) ReadMsg() *Msg {
|
||||
ch := readChunkHeader(r.r)
|
||||
m, ok := r.Msg[ch.csid]
|
||||
if !ok {
|
||||
//l.Printf("chunk: new")
|
||||
m = &Msg{ch, &bytes.Buffer{}, false, 0}
|
||||
r.Msg[ch.csid] = m
|
||||
}
|
||||
|
||||
switch ch.cfmt {
|
||||
case 0:
|
||||
m.ts = ch.ts
|
||||
m.mlen = ch.mlen
|
||||
m.typeid = ch.typeid
|
||||
m.curts = m.ts
|
||||
case 1:
|
||||
m.tsdelta = ch.tsdelta
|
||||
m.mlen = ch.mlen
|
||||
m.typeid = ch.typeid
|
||||
m.curts += m.tsdelta
|
||||
case 2:
|
||||
m.tsdelta = ch.tsdelta
|
||||
}
|
||||
|
||||
left := m.mlen - m.data.Len()
|
||||
size := 128
|
||||
if size > left {
|
||||
size = left
|
||||
}
|
||||
//l.Printf("chunk: %v", m)
|
||||
if size > 0 {
|
||||
io.CopyN(m.data, r.r, int64(size))
|
||||
}
|
||||
|
||||
if size == left {
|
||||
rm := new(Msg)
|
||||
*rm = *m
|
||||
l.Printf("event: fmt%d %v curts %d pre %v", ch.cfmt, m, m.curts, m.data.Bytes()[:9])
|
||||
if m.typeid == MSG_VIDEO && int(m.data.Bytes()[0]) == 0x17 {
|
||||
rm.key = true
|
||||
} else {
|
||||
rm.key = false
|
||||
}
|
||||
m.data = &bytes.Buffer{}
|
||||
return rm
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
449
server.go
Normal file
449
server.go
Normal file
@ -0,0 +1,449 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"bufio"
|
||||
"log"
|
||||
"time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
event = make(chan eventS, 0)
|
||||
eventDone = make(chan int, 0)
|
||||
)
|
||||
|
||||
type eventS struct {
|
||||
id int
|
||||
mr *MsgStream
|
||||
m *Msg
|
||||
}
|
||||
|
||||
type eventID int
|
||||
|
||||
func (e eventS) String() string {
|
||||
switch e.id {
|
||||
case E_NEW:
|
||||
return "new"
|
||||
case E_PUBLISH:
|
||||
return "publish"
|
||||
case E_PLAY:
|
||||
return "play"
|
||||
case E_DATA:
|
||||
return fmt.Sprintf("data %d bytes ts %d", e.m.data.Len(), e.m.curts)
|
||||
case E_CLOSE:
|
||||
return "close"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/*
|
||||
server:
|
||||
connect
|
||||
createStream
|
||||
publish
|
||||
client:
|
||||
connect
|
||||
createStream
|
||||
getStreamLength
|
||||
play
|
||||
*/
|
||||
|
||||
const (
|
||||
E_NEW = iota
|
||||
E_PUBLISH
|
||||
E_PLAY
|
||||
E_DATA
|
||||
E_CLOSE
|
||||
)
|
||||
|
||||
|
||||
func handleConnect(mr *MsgStream, trans float64, app string) {
|
||||
|
||||
l.Printf("stream %v: connect: %s", mr, app)
|
||||
|
||||
mr.app = app
|
||||
|
||||
mr.WriteMsg32(2, MSG_ACK_SIZE, 0, 5000000)
|
||||
mr.WriteMsg32(2, MSG_BANDWIDTH, 0, 5000000)
|
||||
mr.WriteMsg32(2, MSG_CHUNK_SIZE, 0, 128)
|
||||
|
||||
mr.WriteAMFCmd(3, 0, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "_result", },
|
||||
AMFObj { atype : AMF_NUMBER, f64 : trans, },
|
||||
AMFObj { atype : AMF_OBJECT,
|
||||
obj : map[string] AMFObj {
|
||||
"fmtVer" : AMFObj { atype : AMF_STRING, str : "FMS/3,0,1,123", },
|
||||
"capabilities" : AMFObj { atype : AMF_NUMBER, f64 : 31, },
|
||||
},
|
||||
},
|
||||
AMFObj { atype : AMF_OBJECT,
|
||||
obj : map[string] AMFObj {
|
||||
"level" : AMFObj { atype : AMF_STRING, str : "status", },
|
||||
"code" : AMFObj { atype : AMF_STRING, str : "NetConnection.Connect.Success", },
|
||||
"description" : AMFObj { atype : AMF_STRING, str : "Connection Success.", },
|
||||
"objectEncoding" : AMFObj { atype : AMF_NUMBER, f64 : 0, },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func handleMeta(mr *MsgStream, obj AMFObj) {
|
||||
|
||||
mr.W = int(obj.obj["width"].f64)
|
||||
mr.H = int(obj.obj["height"].f64)
|
||||
|
||||
l.Printf("stream %v: meta video %dx%d", mr, mr.W, mr.H)
|
||||
}
|
||||
|
||||
func handleCreateStream(mr *MsgStream, trans float64) {
|
||||
|
||||
l.Printf("stream %v: createStream", mr)
|
||||
|
||||
mr.WriteAMFCmd(3, 0, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "_result", },
|
||||
AMFObj { atype : AMF_NUMBER, f64 : trans, },
|
||||
AMFObj { atype : AMF_NULL, },
|
||||
AMFObj { atype : AMF_NUMBER, f64 : 1 },
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetStreamLength(mr *MsgStream, trans float64) {
|
||||
}
|
||||
|
||||
func handlePublish(mr *MsgStream) {
|
||||
|
||||
l.Printf("stream %v: publish", mr)
|
||||
|
||||
mr.WriteAMFCmd(3, 0, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "onStatus", },
|
||||
AMFObj { atype : AMF_NUMBER, f64 : 0, },
|
||||
AMFObj { atype : AMF_NULL, },
|
||||
AMFObj { atype : AMF_OBJECT,
|
||||
obj : map[string] AMFObj {
|
||||
"level" : AMFObj { atype : AMF_STRING, str : "status", },
|
||||
"code" : AMFObj { atype : AMF_STRING, str : "NetStream.Publish.Start", },
|
||||
"description" : AMFObj { atype : AMF_STRING, str : "Start publising.", },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
event <- eventS{id:E_PUBLISH, mr:mr}
|
||||
<-eventDone
|
||||
}
|
||||
|
||||
type testsrc struct {
|
||||
r *bufio.Reader
|
||||
dir string
|
||||
w,h int
|
||||
ts int
|
||||
codec string
|
||||
key bool
|
||||
idx int
|
||||
data []byte
|
||||
}
|
||||
|
||||
func tsrcNew() (m *testsrc) {
|
||||
m = &testsrc{}
|
||||
m.dir = "/pixies/go/data/tmp"
|
||||
fi, _ := os.Open(fmt.Sprintf("%s/index", m.dir))
|
||||
m.r = bufio.NewReader(fi)
|
||||
l, _ := m.r.ReadString('\n')
|
||||
fmt.Sscanf(l, "%dx%d", &m.w, &m.h)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *testsrc) fetch() (err error) {
|
||||
l, err := m.r.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := strings.Split(l, ",")
|
||||
fmt.Sscanf(a[0], "%d", &m.ts)
|
||||
m.codec = a[1]
|
||||
fmt.Sscanf(a[2], "%d", &m.idx)
|
||||
switch m.codec {
|
||||
case "h264":
|
||||
fmt.Sscanf(a[3], "%t", &m.key)
|
||||
m.data, err = ioutil.ReadFile(fmt.Sprintf("%s/h264/%d.264", m.dir, m.idx))
|
||||
case "aac":
|
||||
m.data, err = ioutil.ReadFile(fmt.Sprintf("%s/aac/%d.aac", m.dir, m.idx))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handlePlay(mr *MsgStream, strid int) {
|
||||
|
||||
l.Printf("stream %v: play", mr)
|
||||
|
||||
var tsrc *testsrc
|
||||
//tsrc = tsrcNew()
|
||||
|
||||
if tsrc == nil {
|
||||
event <- eventS{id:E_PLAY, mr:mr}
|
||||
<-eventDone
|
||||
} else {
|
||||
l.Printf("stream %v: test play data in %s", mr, tsrc.dir)
|
||||
mr.W = tsrc.w
|
||||
mr.H = tsrc.h
|
||||
l.Printf("stream %v: test video %dx%d", mr, mr.W, mr.H)
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
WriteInt(&b, 0, 2)
|
||||
WriteInt(&b, strid, 4)
|
||||
mr.WriteMsg(0, 2, MSG_USER, 0, 0, b.Bytes()) // stream begin 1
|
||||
|
||||
mr.WriteAMFCmd(5, strid, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "onStatus", },
|
||||
AMFObj { atype : AMF_NUMBER, f64 : 0, },
|
||||
AMFObj { atype : AMF_NULL, },
|
||||
AMFObj { atype : AMF_OBJECT,
|
||||
obj : map[string] AMFObj {
|
||||
"level" : AMFObj { atype : AMF_STRING, str : "status", },
|
||||
"code" : AMFObj { atype : AMF_STRING, str : "NetStream.Play.Start", },
|
||||
"description" : AMFObj { atype : AMF_STRING, str : "Start live.", },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
l.Printf("stream %v: video size %dx%d", mr, mr.W, mr.H)
|
||||
|
||||
mr.WriteAMFMeta(5, strid, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "|RtmpSampleAccess", },
|
||||
AMFObj { atype : AMF_BOOLEAN, i: 1, },
|
||||
AMFObj { atype : AMF_BOOLEAN, i: 1, },
|
||||
})
|
||||
|
||||
mr.WriteAMFMeta(5, strid, []AMFObj {
|
||||
AMFObj { atype : AMF_STRING, str : "onMetaData", },
|
||||
AMFObj { atype : AMF_OBJECT,
|
||||
obj : map[string] AMFObj {
|
||||
"Server" : AMFObj { atype : AMF_STRING, str : "Golang Rtmp Server", },
|
||||
"width" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.W), },
|
||||
"height" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.H), },
|
||||
"displayWidth" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.W), },
|
||||
"displayHeight" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.H), },
|
||||
"duration" : AMFObj { atype : AMF_NUMBER, f64 : 0, },
|
||||
"framerate" : AMFObj { atype : AMF_NUMBER, f64 : 25000, },
|
||||
"videodatarate" : AMFObj { atype : AMF_NUMBER, f64 : 731, },
|
||||
"videocodecid" : AMFObj { atype : AMF_NUMBER, f64 : 7, },
|
||||
"audiodatarate" : AMFObj { atype : AMF_NUMBER, f64 : 122, },
|
||||
"audiocodecid" : AMFObj { atype : AMF_NUMBER, f64 : 10, },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if tsrc == nil {
|
||||
l.Printf("stream %v: extra size %d %d", mr, len(mr.extraA), len(mr.extraV))
|
||||
|
||||
mr.WriteAAC(strid, 0, mr.extraA[2:])
|
||||
mr.WritePPS(strid, 0, mr.extraV[5:])
|
||||
|
||||
l.Printf("stream %v: player wait data", mr)
|
||||
|
||||
for {
|
||||
m := <-mr.que
|
||||
l.Printf("data %v: got %v", mr, m)
|
||||
switch m.typeid {
|
||||
case MSG_AUDIO:
|
||||
mr.WriteAudio(strid, m.curts, m.data.Bytes()[2:])
|
||||
case MSG_VIDEO:
|
||||
mr.WriteVideo(strid, m.curts, m.key, m.data.Bytes()[5:])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
lf, _ := os.Create("/tmp/rtmp.log")
|
||||
ll := log.New(lf, "", 0)
|
||||
|
||||
starttm := time.Now()
|
||||
k := 0
|
||||
|
||||
for {
|
||||
err := tsrc.fetch()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch tsrc.codec {
|
||||
case "h264":
|
||||
if tsrc.idx == 0 {
|
||||
mr.WritePPS(strid, 0, tsrc.data)
|
||||
} else {
|
||||
mr.WriteVideo(strid, tsrc.ts, tsrc.key, tsrc.data)
|
||||
}
|
||||
case "aac":
|
||||
if tsrc.idx == 0 {
|
||||
mr.WriteAAC(strid, 0, tsrc.data)
|
||||
} else {
|
||||
mr.WriteAudio(strid, tsrc.ts, tsrc.data)
|
||||
}
|
||||
}
|
||||
dur := time.Since(starttm).Nanoseconds()
|
||||
diff := tsrc.ts - 1000 - int(dur/1000000)
|
||||
if diff > 0 {
|
||||
time.Sleep(time.Duration(diff)*time.Millisecond)
|
||||
}
|
||||
l.Printf("data %v: ts %v dur %v diff %v", mr, tsrc.ts, int(dur/1000000), diff)
|
||||
ll.Printf("#%d %d,%s,%d %d", k, tsrc.ts, tsrc.codec, tsrc.idx, len(tsrc.data))
|
||||
k++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serve(mr *MsgStream) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
event <- eventS{id:E_CLOSE, mr:mr}
|
||||
<-eventDone
|
||||
l.Printf("stream %v: closed %v", mr, err)
|
||||
}
|
||||
}()
|
||||
|
||||
handShake(mr.r)
|
||||
|
||||
// f, _ := os.Create("/tmp/pub.log")
|
||||
// mr.l = log.New(f, "", 0)
|
||||
|
||||
for {
|
||||
m := mr.ReadMsg()
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
//l.Printf("stream %v: msg %v", mr, m)
|
||||
|
||||
if m.typeid == MSG_AUDIO || m.typeid == MSG_VIDEO {
|
||||
// mr.l.Printf("%d,%d", m.typeid, m.data.Len())
|
||||
event <- eventS{id:E_DATA, mr:mr, m:m}
|
||||
<-eventDone
|
||||
}
|
||||
|
||||
if m.typeid == MSG_AMF_CMD || m.typeid == MSG_AMF_META {
|
||||
a := ReadAMF(m.data)
|
||||
//l.Printf("server: amfobj %v\n", a)
|
||||
switch a.str {
|
||||
case "connect":
|
||||
a2 := ReadAMF(m.data)
|
||||
a3 := ReadAMF(m.data)
|
||||
if _, ok := a3.obj["app"]; !ok || a3.obj["app"].str == "" {
|
||||
panic("connect: app not found")
|
||||
}
|
||||
handleConnect(mr, a2.f64, a3.obj["app"].str)
|
||||
case "@setDataFrame":
|
||||
ReadAMF(m.data)
|
||||
a3 := ReadAMF(m.data)
|
||||
handleMeta(mr, a3)
|
||||
l.Printf("stream %v: setdataframe", mr)
|
||||
case "createStream":
|
||||
a2 := ReadAMF(m.data)
|
||||
handleCreateStream(mr, a2.f64)
|
||||
case "publish":
|
||||
handlePublish(mr)
|
||||
case "play":
|
||||
handlePlay(mr, m.strid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listenEvent() {
|
||||
idmap := map[string]*MsgStream{}
|
||||
pubmap := map[string]*MsgStream{}
|
||||
|
||||
for {
|
||||
e := <-event
|
||||
if e.id == E_DATA {
|
||||
l.Printf("data %v: %v", e.mr, e)
|
||||
} else {
|
||||
l.Printf("event %v: %v", e.mr, e)
|
||||
}
|
||||
switch {
|
||||
case e.id == E_NEW:
|
||||
idmap[e.mr.id] = e.mr
|
||||
case e.id == E_PUBLISH:
|
||||
if _, ok := pubmap[e.mr.app]; ok {
|
||||
l.Printf("event %v: duplicated publish with %v app %s", e.mr, pubmap[e.mr.app], e.mr.app)
|
||||
e.mr.Close()
|
||||
} else {
|
||||
e.mr.role = PUBLISHER
|
||||
pubmap[e.mr.app] = e.mr
|
||||
}
|
||||
case e.id == E_PLAY:
|
||||
src, ok := pubmap[e.mr.app]
|
||||
if !ok || src.stat != WAIT_DATA {
|
||||
l.Printf("event %v: cannot find publisher with app %s", e.mr, e.mr.app)
|
||||
e.mr.Close()
|
||||
} else {
|
||||
e.mr.W = src.W
|
||||
e.mr.H = src.H
|
||||
e.mr.role = PLAYER
|
||||
e.mr.extraA = src.extraA
|
||||
e.mr.extraV = src.extraV
|
||||
e.mr.que = make(chan *Msg, 16)
|
||||
}
|
||||
case e.id == E_CLOSE:
|
||||
if e.mr.role == PUBLISHER {
|
||||
delete(pubmap, e.mr.app)
|
||||
}
|
||||
delete(idmap, e.mr.id)
|
||||
case e.id == E_DATA && e.mr.stat == WAIT_EXTRA:
|
||||
if len(e.mr.extraA) == 0 && e.m.typeid == MSG_AUDIO {
|
||||
l.Printf("event %v: got aac config", e.mr)
|
||||
e.mr.extraA = e.m.data.Bytes()
|
||||
}
|
||||
if len(e.mr.extraV) == 0 && e.m.typeid == MSG_VIDEO {
|
||||
l.Printf("event %v: got pps", e.mr)
|
||||
e.mr.extraV = e.m.data.Bytes()
|
||||
}
|
||||
if len(e.mr.extraA) > 0 && len(e.mr.extraV) > 0 {
|
||||
l.Printf("event %v: got all extra", e.mr)
|
||||
e.mr.stat = WAIT_DATA
|
||||
}
|
||||
case e.id == E_DATA && e.mr.stat == WAIT_DATA:
|
||||
for _, mr := range idmap {
|
||||
if mr.role == PLAYER && mr.app == e.mr.app {
|
||||
ch := reflect.ValueOf(mr.que)
|
||||
ok := ch.TrySend(reflect.ValueOf(e.m))
|
||||
if !ok {
|
||||
l.Printf("event %v: send failed", e.mr)
|
||||
} else {
|
||||
l.Printf("event %v: send ok", e.mr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventDone <- 1
|
||||
}
|
||||
}
|
||||
|
||||
func SimpleServer() {
|
||||
l.Printf("server: simple server starts")
|
||||
ln, err := net.Listen("tcp", ":1935")
|
||||
if err != nil {
|
||||
l.Printf("server: error: listen 1935 %s\n", err)
|
||||
return
|
||||
}
|
||||
go listenEvent()
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
l.Printf("server: error: sock accept %s\n", err)
|
||||
break
|
||||
}
|
||||
go func (c net.Conn) {
|
||||
mr := NewMsgStream(c)
|
||||
event <- eventS{id:E_NEW, mr:mr}
|
||||
<-eventDone
|
||||
serve(mr)
|
||||
} (c)
|
||||
}
|
||||
}
|
||||
|
113
util.go
Normal file
113
util.go
Normal file
@ -0,0 +1,113 @@
|
||||
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dummyWriter struct {
|
||||
}
|
||||
|
||||
func (m *dummyWriter) Write(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type logger int
|
||||
|
||||
func (l logger) Printf(format string, v ...interface{}) {
|
||||
str := fmt.Sprintf(format, v...)
|
||||
switch {
|
||||
case strings.HasPrefix(str, "server") && l >= 0,
|
||||
strings.HasPrefix(str, "stream") && l >= 0,
|
||||
strings.HasPrefix(str, "event") && l >= 0,
|
||||
strings.HasPrefix(str, "data") && l >= 0,
|
||||
strings.HasPrefix(str, "msg") && l >= 1:
|
||||
l2.Println(str)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
dummyW = &dummyWriter{}
|
||||
l = logger(0)
|
||||
l2 *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
l2 = log.New(os.Stderr, "", 0)
|
||||
l2.SetFlags(log.Lmicroseconds)
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
r io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (s stream) Read(p []byte) (n int, err error) {
|
||||
n, err = s.r.Read(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s stream) Write(p []byte) (n int, err error) {
|
||||
n, err = s.r.Write(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s stream) Close() {
|
||||
s.r.Close()
|
||||
}
|
||||
|
||||
func ReadBuf(r io.Reader, n int) (b []byte) {
|
||||
b = make([]byte, n)
|
||||
r.Read(b)
|
||||
return
|
||||
}
|
||||
|
||||
func ReadInt(r io.Reader, n int) (ret int) {
|
||||
b := ReadBuf(r, n)
|
||||
for i := 0; i < n; i++ {
|
||||
ret <<= 8
|
||||
ret += int(b[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadIntLE(r io.Reader, n int) (ret int) {
|
||||
b := ReadBuf(r, n)
|
||||
for i := 0; i < n; i++ {
|
||||
ret <<= 8
|
||||
ret += int(b[n-i-1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func WriteBuf(w io.Writer, buf []byte) {
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func WriteInt(w io.Writer, v int, n int) {
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
b[n-i-1] = byte(v&0xff)
|
||||
v >>= 8
|
||||
}
|
||||
WriteBuf(w, b)
|
||||
}
|
||||
|
||||
func WriteIntLE(w io.Writer, v int, n int) {
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
b[i] = byte(v&0xff)
|
||||
v >>= 8
|
||||
}
|
||||
WriteBuf(w, b)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user