From fc3c9d7af380d06b2fccc8ea85c53ec074842df5 Mon Sep 17 00:00:00 2001 From: cfanfrank Date: Sun, 17 Mar 2013 23:41:45 +0800 Subject: [PATCH 01/24] first --- .gitignore | 1 + README.md | 22 +++ amf.go | 100 ++++++++++++ amf_test.go | 21 +++ handshake.go | 145 +++++++++++++++++ msg.go | 299 ++++++++++++++++++++++++++++++++++ server.go | 449 +++++++++++++++++++++++++++++++++++++++++++++++++++ util.go | 113 +++++++++++++ 8 files changed, 1150 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 amf.go create mode 100644 amf_test.go create mode 100644 handshake.go create mode 100644 msg.go create mode 100644 server.go create mode 100644 util.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a01ee28 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..3049b0c --- /dev/null +++ b/README.md @@ -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 + diff --git a/amf.go b/amf.go new file mode 100644 index 0000000..cd465ba --- /dev/null +++ b/amf.go @@ -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) + } +} + diff --git a/amf_test.go b/amf_test.go new file mode 100644 index 0000000..57b59d3 --- /dev/null +++ b/amf_test.go @@ -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) +} + diff --git a/handshake.go b/handshake.go new file mode 100644 index 0000000..d6ae30c --- /dev/null +++ b/handshake.go @@ -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") +} + diff --git a/msg.go b/msg.go new file mode 100644 index 0000000..5e8a457 --- /dev/null +++ b/msg.go @@ -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 +} + diff --git a/server.go b/server.go new file mode 100644 index 0000000..f77c645 --- /dev/null +++ b/server.go @@ -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) + } +} + diff --git a/util.go b/util.go new file mode 100644 index 0000000..0787fca --- /dev/null +++ b/util.go @@ -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) +} + From b9aec58cddd507c836f17ae9d6bc8e4ed0b713ca Mon Sep 17 00:00:00 2001 From: go-av <117270865@qq.com> Date: Sun, 17 Mar 2013 23:43:00 +0800 Subject: [PATCH 02/24] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3049b0c..216f91f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ Rtmp +==== Golang rtmp server From 7aeca4e9599a4560e72ee2b3d92094418bf982bd Mon Sep 17 00:00:00 2001 From: cfanfrank Date: Wed, 20 Mar 2013 23:44:56 +0800 Subject: [PATCH 03/24] switch streams --- handshake.go | 16 ++--- msg.go | 1 + server.go | 199 ++++++++++++++++++++++++++++++++++----------------- util.go | 26 +++---- 4 files changed, 157 insertions(+), 85 deletions(-) diff --git a/handshake.go b/handshake.go index d6ae30c..b7645ef 100644 --- a/handshake.go +++ b/handshake.go @@ -97,25 +97,25 @@ func createResp(b []byte, key []byte) { func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) { if b[0] != 0x3 { - l.Printf("handshake: invalid rtmp version\n") + l.Printf("handshake: invalid rtmp version") err = 1 return } epoch := b[1:5] ver := b[5:9] - l.Printf("handshake: epoch %v ver %v\n", epoch, ver) + l.Printf("handshake: epoch %v ver %v", 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") + l.Printf("handshake: digest not found") err = 1 return } } - l.Printf("handshake: offs = %v\n", offs) + l.Printf("handshake: offs = %v", offs) dig = makeDigest(key, b[1+offs:1+offs+32], -1) return @@ -124,22 +124,22 @@ func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) { func handShake(rw io.ReadWriter) { b := ReadBuf(rw, 1537) - l.Printf("handshake: got client chal\n") + l.Printf("handshake: got client chal") dig, err := parseChal(b, clientKey2, serverKey) if err != 0 { return } createChal(b, serverVersion, serverKey2) - l.Printf("handshake: send server chal\n") + l.Printf("handshake: send server chal") rw.Write(b) b = make([]byte, 1536) createResp(b, dig) - l.Printf("handshake: send server resp\n") + l.Printf("handshake: send server resp") rw.Write(b) b = ReadBuf(rw, 1536) - l.Printf("handshake: got client resp\n") + l.Printf("handshake: got client resp") } diff --git a/msg.go b/msg.go index 5e8a457..011bdc6 100644 --- a/msg.go +++ b/msg.go @@ -111,6 +111,7 @@ type MsgStream struct { Msg map[int]*Msg vts, ats int + meta AMFObj id string role int stat int diff --git a/server.go b/server.go index f77c645..ad503d0 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,7 @@ import ( "log" "time" "strings" + _ "runtime/debug" ) var ( @@ -36,7 +37,12 @@ func (e eventS) String() string { case E_PLAY: return "play" case E_DATA: - return fmt.Sprintf("data %d bytes ts %d", e.m.data.Len(), e.m.curts) + switch e.m.typeid { + case MSG_VIDEO: + return fmt.Sprintf("ts %d video %d bytes key %t", e.m.curts, e.m.data.Len(), e.m.key) + case MSG_AUDIO: + return fmt.Sprintf("ts %d audio %d bytes", e.m.curts, e.m.data.Len()) + } case E_CLOSE: return "close" } @@ -63,7 +69,6 @@ const ( E_CLOSE ) - func handleConnect(mr *MsgStream, trans float64, app string) { l.Printf("stream %v: connect: %s", mr, app) @@ -96,6 +101,7 @@ func handleConnect(mr *MsgStream, trans float64, app string) { func handleMeta(mr *MsgStream, obj AMFObj) { + mr.meta = obj mr.W = int(obj.obj["width"].f64) mr.H = int(obj.obj["height"].f64) @@ -195,71 +201,118 @@ func handlePlay(mr *MsgStream, strid int) { 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 + begin := func () { - 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.", }, + 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) + l.Printf("stream %v: begin: video %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 : "|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, }, + mr.meta.obj["Server"] = AMFObj { atype : AMF_STRING, str : "Golang Rtmp Server", } + mr.meta.atype = AMF_OBJECT + l.Printf("stream %v: %v", mr, mr.meta) + mr.WriteAMFMeta(5, strid, []AMFObj { + AMFObj { atype : AMF_STRING, str : "onMetaData", }, + mr.meta, + /* + 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, }, + }, }, - }, - }) + */ + }) + } + + end := func () { + + l.Printf("stream %v: end", mr) + + var b bytes.Buffer + WriteInt(&b, 1, 2) + WriteInt(&b, strid, 4) + mr.WriteMsg(0, 2, MSG_USER, 0, 0, b.Bytes()) // stream eof 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.Stop", }, + "description" : AMFObj { atype : AMF_STRING, str : "Stop live.", }, + }, + }, + }) + } 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:]) + nr := 0 + + for { + m := <-mr.que + if m == nil { + break + } + //if nr == 0 && !m.key { + // continue + //} + if nr == 0 { + begin() + 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("data %v: got %v curts %v", mr, m, m.curts) + 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:]) + } + nr++ } + end() } + } else { + begin() + lf, _ := os.Create("/tmp/rtmp.log") ll := log.New(lf, "", 0) @@ -304,6 +357,9 @@ func serve(mr *MsgStream) { event <- eventS{id:E_CLOSE, mr:mr} <-eventDone l.Printf("stream %v: closed %v", mr, err) + //if err != "EOF" { + // l.Printf("stream %v: %v", mr, string(debug.Stack())) + //} } }() @@ -377,21 +433,27 @@ func listenEvent() { 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) + e.mr.role = PLAYER + e.mr.que = make(chan *Msg, 16) + for _, mr := range idmap { + if mr.role == PUBLISHER && mr.app == e.mr.app && mr.stat == WAIT_DATA { + e.mr.W = mr.W + e.mr.H = mr.H + e.mr.extraA = mr.extraA + e.mr.extraV = mr.extraV + e.mr.meta = mr.meta + } } case e.id == E_CLOSE: if e.mr.role == PUBLISHER { delete(pubmap, e.mr.app) + for _, mr := range idmap { + if mr.role == PLAYER && mr.app == e.mr.app { + ch := reflect.ValueOf(mr.que) + var m *Msg = nil + ch.TrySend(reflect.ValueOf(m)) + } + } } delete(idmap, e.mr.id) case e.id == E_DATA && e.mr.stat == WAIT_EXTRA: @@ -406,6 +468,15 @@ func listenEvent() { 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 + for _, mr := range idmap { + if mr.role == PLAYER && mr.app == e.mr.app { + mr.W = e.mr.W + mr.H = e.mr.H + mr.extraA = e.mr.extraA + mr.extraV = e.mr.extraV + mr.meta = e.mr.meta + } + } } case e.id == E_DATA && e.mr.stat == WAIT_DATA: for _, mr := range idmap { diff --git a/util.go b/util.go index 0787fca..d22e617 100644 --- a/util.go +++ b/util.go @@ -9,29 +9,25 @@ import ( "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: + case strings.HasPrefix(str, "server") && l >= 1, + strings.HasPrefix(str, "stream") && l >= 1, + strings.HasPrefix(str, "event") && l >= 1, + strings.HasPrefix(str, "data") && l >= 1, + strings.HasPrefix(str, "msg") && l >= 2: l2.Println(str) + default: + if l >= 1 { + l2.Println(str) + } } } var ( - dummyW = &dummyWriter{} l = logger(0) l2 *log.Logger ) @@ -41,6 +37,10 @@ func init() { l2.SetFlags(log.Lmicroseconds) } +func LogLevel(i int) { + l = logger(i) +} + type stream struct { r io.ReadWriteCloser } From 17f1aca838c62ca906c708642a08e1bf1aeead40 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 17 Jun 2016 17:38:03 +0800 Subject: [PATCH 04/24] add new interface --- new.go | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 new.go diff --git a/new.go b/new.go new file mode 100644 index 0000000..fcabf2e --- /dev/null +++ b/new.go @@ -0,0 +1,367 @@ + +package rtmp + +import ( + "net" + "bufio" + "fmt" + "encoding/hex" + "io" + "github.com/nareix/pio" +) + +type Publisher struct { +} + +type Player struct { +} + +type Server struct { + Addr string + HandlePublish func(*Publisher) + HandlePlay func(*Player) +} + +func (self *Server) handleConn(conn *Conn) (err error) { + if err = conn.Handshake(); err != nil { + return + } + + for { + if err = conn.ReadChunk(); err != nil { + return + } + } + + return +} + +func (self *Server) ListenAndServe() (err error) { + addr := self.Addr + if addr == "" { + addr = ":1935" + } + var tcpaddr *net.TCPAddr + if tcpaddr, err = net.ResolveTCPAddr("tcp", addr); err != nil { + err = fmt.Errorf("rtmp: ListenAndServe: %s", err) + return + } + + var listener *net.TCPListener + if listener, err = net.ListenTCP("tcp", tcpaddr); err != nil { + return + } + + var netconn net.Conn + for { + if netconn, err = listener.Accept(); err != nil { + return + } + + conn := &Conn{} + conn.csmap = make(map[uint32]*chunkStream) + conn.maxChunkSize = 128 + conn.bufr = bufio.NewReaderSize(netconn, 512) + conn.bufw = bufio.NewWriterSize(netconn, 512) + conn.br = pio.NewReader(conn.bufr) + conn.bw = pio.NewWriter(conn.bufw) + go self.handleConn(conn) + } +} + +type Conn struct { + br *pio.Reader + bw *pio.Writer + bufr *bufio.Reader + bufw *bufio.Writer + + maxChunkSize int + + lastcsid uint32 + lastcs *chunkStream + csmap map[uint32]*chunkStream +} + +type chunkStream struct { + TimestampNow uint32 + TimestampDelta uint32 + HasTimestampExt bool + Msgsid uint32 + Msgtypeid uint8 + Msglen uint32 + Msgleft uint32 + Msghdrtype uint8 + Msgdata []byte +} + +func (self *chunkStream) Start() { + self.Msgleft = self.Msglen + self.Msgdata = make([]byte, self.Msglen) +} + +func (self *Conn) ReadChunk() ( err error) { + var msghdrtype uint8 + var csid uint32 + var header uint8 + if header, err = self.br.ReadU8(); err != nil { + return + } + msghdrtype = header>>6 + + csid = uint32(header)&0x3f + switch csid { + default: // Chunk basic header 1 + case 0: // Chunk basic header 2 + var i uint8 + if i, err = self.br.ReadU8(); err != nil { + return + } + csid = uint32(i)+64 + case 1: // Chunk basic header 3 + var i uint16 + if i, err = self.br.ReadU16BE(); err != nil { + return + } + csid = uint32(i)+64 + } + + var cs *chunkStream + if self.lastcs != nil && self.lastcsid == csid { + cs = self.lastcs + } else { + cs = &chunkStream{} + self.csmap[csid] = cs + } + self.lastcs = cs + self.lastcsid = csid + + var timestamp uint32 + + switch msghdrtype { + case 0: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| msg stream id | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message stream id (cont) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 9 Chunk Message Header – Type 0 + if cs.Msgleft != 0 { + err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(11); err != nil { + return + } + timestamp = pio.GetU24BE(h[0:3]) + cs.Msghdrtype = msghdrtype + cs.Msglen = pio.GetU24BE(h[3:6]) + cs.Msgtypeid = h[6] + cs.Msgsid = pio.GetU32BE(h[7:11]) + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.HasTimestampExt = true + } else { + cs.HasTimestampExt = false + } + cs.TimestampNow = timestamp + cs.Start() + + case 1: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 10 Chunk Message Header – Type 1 + if cs.Msgleft != 0 { + err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(7); err != nil { + return + } + timestamp = pio.GetU24BE(h[0:3]) + cs.Msghdrtype = msghdrtype + cs.Msglen = pio.GetU24BE(h[3:6]) + cs.Msgtypeid = h[6] + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.HasTimestampExt = true + } else { + cs.HasTimestampExt = false + } + cs.TimestampDelta = timestamp + cs.TimestampNow += timestamp + cs.Start() + + case 2: + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 11 Chunk Message Header – Type 2 + if cs.Msgleft != 0 { + err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(3); err != nil { + return + } + cs.Msghdrtype = msghdrtype + timestamp = pio.GetU24BE(h[0:3]) + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.HasTimestampExt = true + } else { + cs.HasTimestampExt = false + } + cs.TimestampDelta = timestamp + cs.TimestampNow += timestamp + cs.Start() + + case 3: + if cs.Msgleft == 0 { + switch cs.Msghdrtype { + case 0: + if cs.HasTimestampExt { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.TimestampNow = timestamp + } + case 1, 2: + if cs.HasTimestampExt { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + } else { + timestamp = cs.TimestampDelta + } + cs.TimestampNow += timestamp + } + cs.Start() + } + + default: + err = fmt.Errorf("rtmp: invalid chunk msg header type=%d", msghdrtype) + return + } + + size := int(cs.Msgleft) + if size > self.maxChunkSize { + size = self.maxChunkSize + } + off := cs.Msglen-cs.Msgleft + buf := cs.Msgdata[off:int(off)+size] + if _, err = io.ReadFull(self.br, buf); err != nil { + return + } + cs.Msgleft -= uint32(size) + + if true { + fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", + csid, cs.Msgsid, cs.Msgtypeid, cs.Msghdrtype, cs.Msglen, cs.Msgleft) + } + + if cs.Msgleft == 0 { + if true { + fmt.Println("rtmp: chunk data") + fmt.Print(hex.Dump(cs.Msgdata)) + fmt.Printf("%x\n", cs.Msgdata) + } + } + + return +} + +func (self *Conn) Handshake() (err error) { + // C0 + var version uint8 + if version, err = self.br.ReadU8(); err != nil { + return + } + if version != 0x3 { + err = fmt.Errorf("rtmp: handshake c0: version=%d invalid", version) + return + } + + // S0 + if err = self.bw.WriteU8(0x3); err != nil { + return + } + + random := make([]byte, 1528) + + // S1 + if err = self.bw.WriteU32BE(0); err != nil { + return + } + if err = self.bw.WriteU32BE(0); err != nil { + return + } + if _, err = self.bw.Write(random); err != nil { + return + } + if err = self.bufw.Flush(); err != nil { + return + } + + // C1 + var time uint32 + if time, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = io.ReadFull(self.br, random); err != nil { + return + } + + // S2 + if err = self.bw.WriteU32BE(0); err != nil { + return + } + if err = self.bw.WriteU32BE(time); err != nil { + return + } + if _, err = self.bw.Write(random); err != nil { + return + } + if err = self.bufw.Flush(); err != nil { + return + } + + // C2 + if time, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = io.ReadFull(self.br, random); err != nil { + return + } + + return +} + From 2385aafae2dd9f70bfa706ebe65e6824fce6aed7 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 23 Jun 2016 19:00:33 +0800 Subject: [PATCH 05/24] add message support --- handshake.go | 9 +- new.go | 437 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 354 insertions(+), 92 deletions(-) diff --git a/handshake.go b/handshake.go index b7645ef..31911b3 100644 --- a/handshake.go +++ b/handshake.go @@ -106,6 +106,7 @@ func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) { ver := b[5:9] l.Printf("handshake: epoch %v ver %v", epoch, ver) + // random var offs int if offs = findDigest(b[1:], peerKey, 772); offs == -1 { if offs = findDigest(b[1:], peerKey, 8); offs == -1 { @@ -123,7 +124,7 @@ func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) { func handShake(rw io.ReadWriter) { - b := ReadBuf(rw, 1537) + b := ReadBuf(rw, 1537) // C0+C1 l.Printf("handshake: got client chal") dig, err := parseChal(b, clientKey2, serverKey) if err != 0 { @@ -132,14 +133,14 @@ func handShake(rw io.ReadWriter) { createChal(b, serverVersion, serverKey2) l.Printf("handshake: send server chal") - rw.Write(b) + rw.Write(b) // S0+S1 b = make([]byte, 1536) createResp(b, dig) l.Printf("handshake: send server resp") - rw.Write(b) + rw.Write(b) // S2 - b = ReadBuf(rw, 1536) + b = ReadBuf(rw, 1536) // C2 l.Printf("handshake: got client resp") } diff --git a/new.go b/new.go index fcabf2e..af634eb 100644 --- a/new.go +++ b/new.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "io" "github.com/nareix/pio" + "github.com/nareix/flv/flvio" ) type Publisher struct { @@ -23,16 +24,10 @@ type Server struct { } func (self *Server) handleConn(conn *Conn) (err error) { - if err = conn.Handshake(); err != nil { + if err = conn.determineType(); err != nil { + fmt.Println("rtmp: conn closed:", err) return } - - for { - if err = conn.ReadChunk(); err != nil { - return - } - } - return } @@ -58,13 +53,7 @@ func (self *Server) ListenAndServe() (err error) { return } - conn := &Conn{} - conn.csmap = make(map[uint32]*chunkStream) - conn.maxChunkSize = 128 - conn.bufr = bufio.NewReaderSize(netconn, 512) - conn.bufw = bufio.NewWriterSize(netconn, 512) - conn.br = pio.NewReader(conn.bufr) - conn.bw = pio.NewWriter(conn.bufw) + conn := newConn(netconn) go self.handleConn(conn) } } @@ -74,32 +63,271 @@ type Conn struct { bw *pio.Writer bufr *bufio.Reader bufw *bufio.Writer + intw *pio.Writer - maxChunkSize int + writeMaxChunkSize int + readMaxChunkSize int lastcsid uint32 lastcs *chunkStream csmap map[uint32]*chunkStream + + publishing, playing bool + + gotcommand bool + command string + commandr *pio.Reader + commandobj flvio.AMFMap + commandtransid float64 + + gotmsg bool + msgdata []byte + msgtypeid uint8 +} + +func newConn(netconn net.Conn) *Conn { + conn := &Conn{} + conn.csmap = make(map[uint32]*chunkStream) + conn.readMaxChunkSize = 128 + conn.writeMaxChunkSize = 128 + conn.bufr = bufio.NewReaderSize(netconn, 512) + conn.bufw = bufio.NewWriterSize(netconn, 512) + conn.br = pio.NewReader(conn.bufr) + conn.bw = pio.NewWriter(conn.bufw) + conn.intw = pio.NewWriter(nil) + return conn } type chunkStream struct { - TimestampNow uint32 - TimestampDelta uint32 - HasTimestampExt bool - Msgsid uint32 - Msgtypeid uint8 - Msglen uint32 - Msgleft uint32 - Msghdrtype uint8 - Msgdata []byte + timenow uint32 + timedelta uint32 + hastimeext bool + msgsid uint32 + msgtypeid uint8 + msgdatalen uint32 + msgdataleft uint32 + msghdrtype uint8 + msgdata []byte } func (self *chunkStream) Start() { - self.Msgleft = self.Msglen - self.Msgdata = make([]byte, self.Msglen) + self.msgdataleft = self.msgdatalen + self.msgdata = make([]byte, self.msgdatalen) } -func (self *Conn) ReadChunk() ( err error) { +const ( + msgtypeidUserControl = 4 + msgtypeidWindowAckSize = 5 + msgtypeidSetPeerBandwidth = 6 + msgtypeidSetChunkSize = 1 + msgtypeidCommandMsgAMF0 = 20 + msgtypeidCommandMsgAMF3 = 17 +) + +const ( + eventtypeStreamBegin = 0 +) + +func (self *Conn) pollCommand() (err error) { + for { + if err = self.readChunk(); err != nil { + return + } + if self.gotcommand { + self.gotcommand = false + return + } + } +} + +func (self *Conn) pollMsg() (err error) { + for { + if err = self.readChunk(); err != nil { + return + } + if self.gotmsg { + self.gotmsg = false + return + } + } +} + +func (self *Conn) determineType() (err error) { + if err = self.handshake(); err != nil { + return + } + + // < connect + if err = self.pollCommand(); err != nil { + return + } + if self.command != "connect" { + err = fmt.Errorf("rtmp: first command is not connect") + return + } + + // > WindowAckSize + if err = self.writeWindowAckSize(5000000); err != nil { + return + } + // > SetPeerBandwidth + if err = self.writeSetPeerBandwidth(5000000, 2); err != nil { + return + } + // > SetChunkSize + if err = self.writeSetChunkSize(uint32(self.writeMaxChunkSize)); err != nil { + return + } + + // > _result("NetConnection.Connect.Success") + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "_result") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "fmtVer": "FMS/3,0,1,123", + "capabilities": 31, + }) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "level": "status", + "code": "NetConnection.Connect.Success", + "description": "Connection Success.", + "objectEncoding": 0, + }) + self.writeCommandMsgEnd() + + if err = self.pollCommand(); err != nil { + return + } + if err = self.pollCommand(); err != nil { + return + } + + return +} + +func (self *Conn) writeSetChunkSize(size uint32) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(size) + return self.writeProtoCtrlMsgEnd(msgtypeidSetChunkSize) +} + +func (self *Conn) writeWindowAckSize(size uint32) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(size) + return self.writeProtoCtrlMsgEnd(msgtypeidWindowAckSize) +} + +func (self *Conn) writeSetPeerBandwidth(acksize uint32, limittype uint8) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(acksize) + w.WriteU8(limittype) + return self.writeProtoCtrlMsgEnd(msgtypeidSetPeerBandwidth) +} + +func (self *Conn) writeProtoCtrlMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeProtoCtrlMsgEnd(msgtypeid uint8) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(2, 0, msgtypeid, 0, msgdatav) +} + +func (self *Conn) writeCommandMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeCommandMsgEnd() (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(3, 0, msgtypeidCommandMsgAMF0, 0, msgdatav) +} + +func (self *Conn) writeUserControlMsgStart(eventtype uint16) *pio.Writer { + self.intw.SaveToVecOn() + self.intw.WriteU16BE(eventtype) + return self.intw +} + +func (self *Conn) writeUserControlMsgEnd() (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(2, 0, msgtypeidUserControl, 0, msgdatav) +} + +func (self *Conn) writeStreamBegin(msgcsid uint32) (err error) { + w := self.writeUserControlMsgStart(eventtypeStreamBegin) + w.WriteU32BE(msgcsid) + return self.writeUserControlMsgEnd() +} + +func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, msgcsid uint32, msgdatav [][]byte) (err error) { + msgdatalen := pio.VecLen(msgdatav) + + // [Type 0][Type 3][Type 3][Type 3] + + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| msg stream id | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message stream id (cont) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 9 Chunk Message Header – Type 0 + if err = self.bw.WriteU8(byte(csid)&0x3f); err != nil { + return + } + if err = self.bw.WriteU24BE(timestamp); err != nil { + return + } + if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { + return + } + if err = self.bw.WriteU8(msgtypeid); err != nil { + return + } + if err = self.bw.WriteU32BE(msgcsid); err != nil { + return + } + + msgdataoff := 0 + for { + size := msgdatalen - msgdataoff + if size > self.writeMaxChunkSize { + size = self.writeMaxChunkSize + } + + write := pio.VecSlice(msgdatav, msgdataoff, msgdataoff+size) + for _, b := range write { + if _, err = self.bw.Write(b); err != nil { + return + } + } + + msgdataoff += size + if msgdataoff == msgdatalen { + break + } + + // Type 3 + if err = self.bw.WriteU8(byte(csid)&0x3f|3<<6); err != nil { + return + } + } + + fmt.Printf("rtmp: write chunk msgdatalen=%d\n", msgdatalen) + + if err = self.bufw.Flush(); err != nil { + return + } + + return +} + +func (self *Conn) readChunk() (err error) { var msghdrtype uint8 var csid uint32 var header uint8 @@ -150,8 +378,8 @@ func (self *Conn) ReadChunk() ( err error) { // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Figure 9 Chunk Message Header – Type 0 - if cs.Msgleft != 0 { - err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) return } var h[]byte @@ -159,19 +387,19 @@ func (self *Conn) ReadChunk() ( err error) { return } timestamp = pio.GetU24BE(h[0:3]) - cs.Msghdrtype = msghdrtype - cs.Msglen = pio.GetU24BE(h[3:6]) - cs.Msgtypeid = h[6] - cs.Msgsid = pio.GetU32BE(h[7:11]) + cs.msghdrtype = msghdrtype + cs.msgdatalen = pio.GetU24BE(h[3:6]) + cs.msgtypeid = h[6] + cs.msgsid = pio.GetU32BE(h[7:11]) if timestamp == 0xffffff { if timestamp, err = self.br.ReadU32BE(); err != nil { return } - cs.HasTimestampExt = true + cs.hastimeext = true } else { - cs.HasTimestampExt = false + cs.hastimeext = false } - cs.TimestampNow = timestamp + cs.timenow = timestamp cs.Start() case 1: @@ -184,8 +412,8 @@ func (self *Conn) ReadChunk() ( err error) { // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Figure 10 Chunk Message Header – Type 1 - if cs.Msgleft != 0 { - err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) return } var h[]byte @@ -193,19 +421,19 @@ func (self *Conn) ReadChunk() ( err error) { return } timestamp = pio.GetU24BE(h[0:3]) - cs.Msghdrtype = msghdrtype - cs.Msglen = pio.GetU24BE(h[3:6]) - cs.Msgtypeid = h[6] + cs.msghdrtype = msghdrtype + cs.msgdatalen = pio.GetU24BE(h[3:6]) + cs.msgtypeid = h[6] if timestamp == 0xffffff { if timestamp, err = self.br.ReadU32BE(); err != nil { return } - cs.HasTimestampExt = true + cs.hastimeext = true } else { - cs.HasTimestampExt = false + cs.hastimeext = false } - cs.TimestampDelta = timestamp - cs.TimestampNow += timestamp + cs.timedelta = timestamp + cs.timenow += timestamp cs.Start() case 2: @@ -216,47 +444,47 @@ func (self *Conn) ReadChunk() ( err error) { // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Figure 11 Chunk Message Header – Type 2 - if cs.Msgleft != 0 { - err = fmt.Errorf("rtmp: chunk msgleft=%d invalid", cs.Msgleft) + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) return } var h[]byte if h, err = self.br.ReadBytes(3); err != nil { return } - cs.Msghdrtype = msghdrtype + cs.msghdrtype = msghdrtype timestamp = pio.GetU24BE(h[0:3]) if timestamp == 0xffffff { if timestamp, err = self.br.ReadU32BE(); err != nil { return } - cs.HasTimestampExt = true + cs.hastimeext = true } else { - cs.HasTimestampExt = false + cs.hastimeext = false } - cs.TimestampDelta = timestamp - cs.TimestampNow += timestamp + cs.timedelta = timestamp + cs.timenow += timestamp cs.Start() case 3: - if cs.Msgleft == 0 { - switch cs.Msghdrtype { + if cs.msgdataleft == 0 { + switch cs.msghdrtype { case 0: - if cs.HasTimestampExt { + if cs.hastimeext { if timestamp, err = self.br.ReadU32BE(); err != nil { return } - cs.TimestampNow = timestamp + cs.timenow = timestamp } case 1, 2: - if cs.HasTimestampExt { + if cs.hastimeext { if timestamp, err = self.br.ReadU32BE(); err != nil { return } } else { - timestamp = cs.TimestampDelta + timestamp = cs.timedelta } - cs.TimestampNow += timestamp + cs.timenow += timestamp } cs.Start() } @@ -266,36 +494,75 @@ func (self *Conn) ReadChunk() ( err error) { return } - size := int(cs.Msgleft) - if size > self.maxChunkSize { - size = self.maxChunkSize + size := int(cs.msgdataleft) + if size > self.readMaxChunkSize { + size = self.readMaxChunkSize } - off := cs.Msglen-cs.Msgleft - buf := cs.Msgdata[off:int(off)+size] + off := cs.msgdatalen-cs.msgdataleft + buf := cs.msgdata[off:int(off)+size] if _, err = io.ReadFull(self.br, buf); err != nil { return } - cs.Msgleft -= uint32(size) + cs.msgdataleft -= uint32(size) if true { fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", - csid, cs.Msgsid, cs.Msgtypeid, cs.Msghdrtype, cs.Msglen, cs.Msgleft) + csid, cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) } - if cs.Msgleft == 0 { + if cs.msgdataleft == 0 { if true { fmt.Println("rtmp: chunk data") - fmt.Print(hex.Dump(cs.Msgdata)) - fmt.Printf("%x\n", cs.Msgdata) + fmt.Print(hex.Dump(cs.msgdata)) + fmt.Printf("%x\n", cs.msgdata) + } + + if err = self.handleMsg(cs.msgtypeid, cs.msgdata); err != nil { + return } } return } -func (self *Conn) Handshake() (err error) { - // C0 +func (self *Conn) handleMsg(msgtypeid uint8, msgdata []byte) (err error) { + switch msgtypeid { + case msgtypeidCommandMsgAMF0: + r := pio.NewReaderBytes(msgdata) + + command, _ := flvio.ReadAMF0Val(r) + commandtransid, _ := flvio.ReadAMF0Val(r) + commandobj, _ := flvio.ReadAMF0Val(r) + + var ok bool + if self.command, ok = command.(string); !ok { + err = fmt.Errorf("rtmp: CommandMsgAMF0 command is not string") + return + } + + self.commandobj, _ = commandobj.(flvio.AMFMap) + self.commandtransid, _ = commandtransid.(float64) + + self.commandr = r + self.gotcommand = true + + case msgtypeidSetPeerBandwidth: + case msgtypeidSetChunkSize: + case msgtypeidWindowAckSize: + self.msgdata = msgdata + self.msgtypeid = msgtypeid + self.gotmsg = true + } + + return +} + +func (self *Conn) handshake() (err error) { + var time uint32 var version uint8 + random := make([]byte, 1528) + + // C0 if version, err = self.br.ReadU8(); err != nil { return } @@ -303,14 +570,21 @@ func (self *Conn) Handshake() (err error) { err = fmt.Errorf("rtmp: handshake c0: version=%d invalid", version) return } + // C1 + if time, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = self.br.ReadU32BE(); err != nil { + return + } + if _, err = io.ReadFull(self.br, random); err != nil { + return + } // S0 if err = self.bw.WriteU8(0x3); err != nil { return } - - random := make([]byte, 1528) - // S1 if err = self.bw.WriteU32BE(0); err != nil { return @@ -324,19 +598,6 @@ func (self *Conn) Handshake() (err error) { if err = self.bufw.Flush(); err != nil { return } - - // C1 - var time uint32 - if time, err = self.br.ReadU32BE(); err != nil { - return - } - if _, err = self.br.ReadU32BE(); err != nil { - return - } - if _, err = io.ReadFull(self.br, random); err != nil { - return - } - // S2 if err = self.bw.WriteU32BE(0); err != nil { return From 505ca290d3040c9131d2b9ae4982ea0bd726264d Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 24 Jun 2016 22:09:36 +0800 Subject: [PATCH 06/24] add handle connect, NetConnection.Connect.Success, play, onMetadata --- new.go | 499 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 426 insertions(+), 73 deletions(-) diff --git a/new.go b/new.go index af634eb..eb25ad4 100644 --- a/new.go +++ b/new.go @@ -2,25 +2,27 @@ package rtmp import ( + "bytes" "net" "bufio" + "time" "fmt" "encoding/hex" "io" "github.com/nareix/pio" "github.com/nareix/flv/flvio" + "github.com/nareix/av" + "github.com/nareix/codec/h264parser" + "github.com/nareix/codec/aacparser" + "crypto/hmac" + "crypto/sha256" + "crypto/rand" ) -type Publisher struct { -} - -type Player struct { -} - type Server struct { Addr string - HandlePublish func(*Publisher) - HandlePlay func(*Player) + HandlePublish func(*Conn) + HandlePlay func(*Conn) } func (self *Server) handleConn(conn *Conn) (err error) { @@ -28,6 +30,14 @@ func (self *Server) handleConn(conn *Conn) (err error) { fmt.Println("rtmp: conn closed:", err) return } + + if conn.playing { + if self.HandlePlay != nil { + self.HandlePlay(conn) + conn.Close() + } + } + return } @@ -59,11 +69,15 @@ func (self *Server) ListenAndServe() (err error) { } type Conn struct { + RequestUri string + streams []av.CodecData + br *pio.Reader bw *pio.Writer bufr *bufio.Reader bufw *bufio.Writer intw *pio.Writer + netconn net.Conn writeMaxChunkSize int readMaxChunkSize int @@ -73,20 +87,25 @@ type Conn struct { csmap map[uint32]*chunkStream publishing, playing bool + playmsgcsid uint32 gotcommand bool - command string - commandr *pio.Reader - commandobj flvio.AMFMap + commandname string commandtransid float64 + commandobj flvio.AMFMap + commandparams []interface{} gotmsg bool msgdata []byte msgtypeid uint8 + msgcsid uint32 + + eventtype uint16 } func newConn(netconn net.Conn) *Conn { conn := &Conn{} + conn.netconn = netconn conn.csmap = make(map[uint32]*chunkStream) conn.readMaxChunkSize = 128 conn.writeMaxChunkSize = 128 @@ -122,31 +141,40 @@ const ( msgtypeidSetChunkSize = 1 msgtypeidCommandMsgAMF0 = 20 msgtypeidCommandMsgAMF3 = 17 + msgtypeidDataMsgAMF0 = 18 + msgtypeidDataMsgAMF3 = 15 + msgtypeidVideoMsg = 9 + msgtypeidAudioMsg = 8 ) const ( eventtypeStreamBegin = 0 ) +func (self *Conn) Close() (err error) { + return self.netconn.Close() +} + func (self *Conn) pollCommand() (err error) { for { - if err = self.readChunk(); err != nil { + if err = self.pollMsg(); err != nil { return } if self.gotcommand { - self.gotcommand = false return } } } func (self *Conn) pollMsg() (err error) { + self.gotmsg = false + self.gotcommand = false for { if err = self.readChunk(); err != nil { return } if self.gotmsg { - self.gotmsg = false + fmt.Println("rtmp: gotmsg iscommand", self.gotcommand) return } } @@ -161,7 +189,7 @@ func (self *Conn) determineType() (err error) { if err = self.pollCommand(); err != nil { return } - if self.command != "connect" { + if self.commandname != "connect" { err = fmt.Errorf("rtmp: first command is not connect") return } @@ -191,20 +219,201 @@ func (self *Conn) determineType() (err error) { "level": "status", "code": "NetConnection.Connect.Success", "description": "Connection Success.", - "objectEncoding": 0, + "objectEncoding": 3, }) - self.writeCommandMsgEnd() + self.writeCommandMsgEnd(3, 0) - if err = self.pollCommand(); err != nil { - return - } - if err = self.pollCommand(); err != nil { - return + for { + if err = self.pollMsg(); err != nil { + return + } + if self.gotcommand { + switch self.commandname { + + // < createStream + case "createStream": + self.playmsgcsid = uint32(1) + // > _result(streamid) + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "_result") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, self.playmsgcsid) // streamid=1 + self.writeCommandMsgEnd(3, 0) + + // < play("path") + case "play": + if len(self.commandparams) < 1 { + err = fmt.Errorf("rtmp: play params invalid") + return + } + path, _ := self.commandparams[0].(string) + self.RequestUri = path + fmt.Println("rtmp: play", path) + + // > streamBegin(streamid) + self.writeStreamBegin(self.playmsgcsid) + + // > onStatus() + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "onStatus") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "level": "status", + "code": "NetStream.Play.Start", + "description": "Start live", + }) + self.writeCommandMsgEnd(5, self.playmsgcsid) + + // > |RtmpSampleAccess() + w = self.writeDataMsgStart() + flvio.WriteAMF0Val(w, "|RtmpSampleAccess") + flvio.WriteAMF0Val(w, true) + flvio.WriteAMF0Val(w, true) + self.writeDataMsgEnd(5, self.playmsgcsid) + + fmt.Println("rtmp: playing") + self.playing = true + return + } + + } } return } +func (self *Conn) WritePacket(pkt av.Packet) (err error) { + ts := uint32(pkt.Time/time.Millisecond) + stream := self.streams[pkt.Idx] + + switch stream.Type() { + case av.AAC: + audiodata := self.makeAACAudiodata(stream.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) + w := self.writeAudioDataStart() + audiodata.Marshal(w) + self.writeAudioDataEnd(ts) + + case av.H264: + videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.Data) + w := self.writeVideoDataStart() + videodata.Marshal(w) + self.writeVideoDataEnd(ts) + } + return +} + +func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { + metadata := flvio.AMFMap{} + metadata["Server"] = "joy4" + metadata["duration"] = 0 + + for _, _stream := range streams { + typ := _stream.Type() + switch { + case typ.IsVideo(): + stream := _stream.(av.VideoCodecData) + switch typ { + case av.H264: + metadata["videocodecid"] = flvio.VIDEO_H264 + + default: + err = fmt.Errorf("rtmp: WriteHeader unsupported video codecType=%v", stream.Type()) + return + } + + metadata["width"] = stream.Width() + metadata["height"] = stream.Height() + metadata["displayWidth"] = stream.Width() + metadata["displayHeight"] = stream.Height() + metadata["framerate"] = 24 // TODO: make it correct + metadata["fps"] = 24 + metadata["videodatarate"] = 1538 // TODO: make it correct + metadata["profile"] = "" + metadata["level"] = "" + + case typ.IsAudio(): + stream := _stream.(av.AudioCodecData) + switch typ { + case av.AAC: + metadata["audiocodecid"] = flvio.SOUND_AAC + + default: + err = fmt.Errorf("rtmp: WriteHeader unsupported audio codecType=%v", stream.Type()) + return + } + + metadata["audiodatarate"] = 156 // TODO: make it correct + } + } + + // > onMetaData() + w := self.writeDataMsgStart() + flvio.WriteAMF0Val(w, "onMetaData") + flvio.WriteAMF0Val(w, metadata) + if err = self.writeDataMsgEnd(5, self.playmsgcsid); err != nil { + return + } + + // > Videodata(decoder config) + // > Audiodata(decoder config) + for _, stream := range streams { + switch stream.Type() { + case av.H264: + h264 := stream.(h264parser.CodecData) + videodata := self.makeH264Videodata(flvio.AVC_SEQHDR, h264.AVCDecoderConfRecordBytes()) + w := self.writeVideoDataStart() + videodata.Marshal(w) + if err = self.writeVideoDataEnd(0); err != nil { + return + } + + case av.AAC: + aac := stream.(aacparser.CodecData) + audiodata := self.makeAACAudiodata(aac, flvio.AAC_SEQHDR, aac.MPEG4AudioConfigBytes()) + w := self.writeAudioDataStart() + audiodata.Marshal(w) + if err = self.writeAudioDataEnd(0); err != nil { + return + } + } + } + + self.streams = streams + return +} + +func (self *Conn) makeH264Videodata(pkttype uint8, data []byte) flvio.Videodata { + return flvio.Videodata{ + FrameType: flvio.FRAME_KEY, + CodecID: flvio.VIDEO_H264, + AVCPacketType: pkttype, + Data: data, + } +} + +func (self *Conn) makeAACAudiodata(stream av.AudioCodecData, pkttype uint8, data []byte) flvio.Audiodata { + audiodata := flvio.Audiodata{ + SoundFormat: flvio.SOUND_AAC, + SoundRate: flvio.SOUND_44Khz, + AACPacketType: pkttype, + } + switch stream.SampleFormat().BytesPerSample() { + case 1: + audiodata.SoundSize = flvio.SOUND_8BIT + case 2: + audiodata.SoundSize = flvio.SOUND_16BIT + } + switch stream.ChannelLayout().Count() { + case 1: + audiodata.SoundType = flvio.SOUND_MONO + case 2: + audiodata.SoundType = flvio.SOUND_STEREO + } + return audiodata +} + func (self *Conn) writeSetChunkSize(size uint32) (err error) { w := self.writeProtoCtrlMsgStart() w.WriteU32BE(size) @@ -239,9 +448,39 @@ func (self *Conn) writeCommandMsgStart() *pio.Writer { return self.intw } -func (self *Conn) writeCommandMsgEnd() (err error) { +func (self *Conn) writeCommandMsgEnd(csid uint32, msgcsid uint32) (err error) { msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(3, 0, msgtypeidCommandMsgAMF0, 0, msgdatav) + return self.writeChunks(csid, 0, msgtypeidCommandMsgAMF0, msgcsid, msgdatav) +} + +func (self *Conn) writeDataMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeDataMsgEnd(csid uint32, msgcsid uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(csid, 0, msgtypeidDataMsgAMF0, msgcsid, msgdatav) +} + +func (self *Conn) writeVideoDataStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeVideoDataEnd(timestamp uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(6, timestamp, msgtypeidVideoMsg, self.playmsgcsid, msgdatav) +} + +func (self *Conn) writeAudioDataStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeAudioDataEnd(timestamp uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(6, timestamp, msgtypeidAudioMsg, self.playmsgcsid, msgdatav) } func (self *Conn) writeUserControlMsgStart(eventtype uint16) *pio.Writer { @@ -264,7 +503,7 @@ func (self *Conn) writeStreamBegin(msgcsid uint32) (err error) { func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, msgcsid uint32, msgdatav [][]byte) (err error) { msgdatalen := pio.VecLen(msgdatav) - // [Type 0][Type 3][Type 3][Type 3] + // [Type 0][Type 3][Type 3].... // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -517,7 +756,7 @@ func (self *Conn) readChunk() (err error) { fmt.Printf("%x\n", cs.msgdata) } - if err = self.handleMsg(cs.msgtypeid, cs.msgdata); err != nil { + if err = self.handleMsg(csid, cs.msgtypeid, cs.msgdata); err != nil { return } } @@ -525,60 +764,195 @@ func (self *Conn) readChunk() (err error) { return } -func (self *Conn) handleMsg(msgtypeid uint8, msgdata []byte) (err error) { +func (self *Conn) handleCommandMsgAMF0(r *pio.Reader) (err error) { + commandname, _ := flvio.ReadAMF0Val(r) + commandtransid, _ := flvio.ReadAMF0Val(r) + commandobj, _ := flvio.ReadAMF0Val(r) + + var ok bool + if self.commandname, ok = commandname.(string); !ok { + err = fmt.Errorf("rtmp: CommandMsgAMF0 command is not string") + return + } + + self.commandobj, _ = commandobj.(flvio.AMFMap) + self.commandtransid, _ = commandtransid.(float64) + self.commandparams = []interface{}{} + for { + if val, rerr := flvio.ReadAMF0Val(r); rerr != nil { + break + } else { + self.commandparams = append(self.commandparams, val) + } + } + + self.gotcommand = true + return +} + +func (self *Conn) handleMsg(msgcsid uint32, msgtypeid uint8, msgdata []byte) (err error) { + self.msgcsid = msgcsid + switch msgtypeid { case msgtypeidCommandMsgAMF0: r := pio.NewReaderBytes(msgdata) - - command, _ := flvio.ReadAMF0Val(r) - commandtransid, _ := flvio.ReadAMF0Val(r) - commandobj, _ := flvio.ReadAMF0Val(r) - - var ok bool - if self.command, ok = command.(string); !ok { - err = fmt.Errorf("rtmp: CommandMsgAMF0 command is not string") + if err = self.handleCommandMsgAMF0(r); err != nil { return } - self.commandobj, _ = commandobj.(flvio.AMFMap) - self.commandtransid, _ = commandtransid.(float64) + case msgtypeidCommandMsgAMF3: + r := pio.NewReaderBytes(msgdata) + r.ReadU8() // skip first byte + if err = self.handleCommandMsgAMF0(r); err != nil { + return + } - self.commandr = r - self.gotcommand = true + case msgtypeidUserControl: + if len(msgdata) >= 2 { + self.eventtype = pio.GetU16BE(msgdata) + } else { + err = fmt.Errorf("rtmp: short packet of UserControl") + return + } case msgtypeidSetPeerBandwidth: case msgtypeidSetChunkSize: case msgtypeidWindowAckSize: self.msgdata = msgdata self.msgtypeid = msgtypeid - self.gotmsg = true + + default: + return } + self.gotmsg = true return } +var ( + hsClientFullKey = []byte{ + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', + '0', '0', '1', + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, + } + hsServerFullKey = []byte{ + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', + 'S', 'e', 'r', 'v', 'e', 'r', ' ', + '0', '0', '1', + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, + } + hsClientPartialKey = hsClientFullKey[:30] + hsServerPartialKey = hsServerFullKey[:36] +) + +func hsMakeDigest(key []byte, src []byte, 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 hsFindDigest(p []byte, key []byte, base int) (off int) { + for n := 0; n < 4; n++ { + off += int(p[base + n]) + } + off = (off % 728) + base + 4 + digest := hsMakeDigest(key, p, off) + if bytes.Compare(p[off:off+32], digest) != 0 { + off = -1 + } + return off +} + +func hsParseC1(p []byte) (digest []byte, err error) { + return hsParse1(p, hsClientPartialKey) +} + +func hsParseS1(p []byte) (digest []byte, err error) { + return hsParse1(p, hsServerPartialKey) +} + +func hsParse1(p []byte, key []byte) (digest []byte, err error) { + var off int + if off = hsFindDigest(p, key, 772); off == -1 { + if off = hsFindDigest(p, key, 8); off == -1 { + err = fmt.Errorf("rtmp: handshake: C1 parse failed") + return + } + } + digest = hsMakeDigest(key, p[off:off+32], -1) + return +} + +func hsCreateS1(p []byte) { + hsCreate1(p, hsServerPartialKey) +} + +func hsCreateS2(p []byte, digest []byte) { + rand.Read(p) + digest2 := hsMakeDigest(digest, p, 1536-32) + copy(p[1536-32:], digest2) +} + +func hsCreate1(p []byte, key []byte) { + rand.Read(p) + off := 0 + for n := 8; n < 12; n++ { + off += int(p[n]) + } + off = (off % 728) + 12 + digest := hsMakeDigest(key, p, off) + copy(p[off:], digest) +} + func (self *Conn) handshake() (err error) { - var time uint32 var version uint8 - random := make([]byte, 1528) + + var random [1536*4]byte + var digest []byte + C1 := random[0:1536] + S1 := random[1536:1536*2] + C2 := random[1536*2:1536*3] + S2 := random[1536*3:1536*4] // C0 if version, err = self.br.ReadU8(); err != nil { return } if version != 0x3 { - err = fmt.Errorf("rtmp: handshake c0: version=%d invalid", version) + err = fmt.Errorf("rtmp: handshake C0: version=%d invalid", version) return } // C1 - if time, err = self.br.ReadU32BE(); err != nil { + if _, err = io.ReadFull(self.br, C1); err != nil { return } - if _, err = self.br.ReadU32BE(); err != nil { - return - } - if _, err = io.ReadFull(self.br, random); err != nil { - return + + // TODO: do the right thing + if false { + if digest, err = hsParseC1(C1); err != nil { + return + } + serverTime := uint32(0) + serverVer := uint32(0x0d0e0a0d) + hsCreateS1(S1) + pio.PutU32BE(S1[0:4], serverTime) + pio.PutU32BE(S1[4:8], serverVer) + hsCreateS2(S2, digest) } // S0 @@ -586,26 +960,11 @@ func (self *Conn) handshake() (err error) { return } // S1 - if err = self.bw.WriteU32BE(0); err != nil { - return - } - if err = self.bw.WriteU32BE(0); err != nil { - return - } - if _, err = self.bw.Write(random); err != nil { - return - } - if err = self.bufw.Flush(); err != nil { + if _, err = self.bw.Write(S1); err != nil { return } // S2 - if err = self.bw.WriteU32BE(0); err != nil { - return - } - if err = self.bw.WriteU32BE(time); err != nil { - return - } - if _, err = self.bw.Write(random); err != nil { + if _, err = self.bw.Write(S2); err != nil { return } if err = self.bufw.Flush(); err != nil { @@ -613,13 +972,7 @@ func (self *Conn) handshake() (err error) { } // C2 - if time, err = self.br.ReadU32BE(); err != nil { - return - } - if _, err = self.br.ReadU32BE(); err != nil { - return - } - if _, err = io.ReadFull(self.br, random); err != nil { + if _, err = io.ReadFull(self.br, C2); err != nil { return } From ec5094fd7db8445cdd4b6294f3b12fd0d5607c02 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 25 Jun 2016 22:59:53 +0800 Subject: [PATCH 07/24] Delete README.md --- README.md | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 216f91f..0000000 --- a/README.md +++ /dev/null @@ -1,23 +0,0 @@ -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 - From fd701bddc1e5174926e441ca1470e63723006fb8 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 19:38:14 +0800 Subject: [PATCH 08/24] add many things --- server.go | 1551 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 1079 insertions(+), 472 deletions(-) diff --git a/server.go b/server.go index ad503d0..a34bfa7 100644 --- a/server.go +++ b/server.go @@ -2,519 +2,1126 @@ package rtmp import ( - "bytes" - "net" - "fmt" - "reflect" - "io/ioutil" - "os" - "bufio" - "log" - "time" "strings" - _ "runtime/debug" + _"bytes" + "net" + "net/url" + "bufio" + "time" + "fmt" + "encoding/hex" + "io" + "github.com/nareix/pio" + "github.com/nareix/flv/flvio" + "github.com/nareix/av" + "github.com/nareix/codec/h264parser" + "github.com/nareix/codec/aacparser" + "crypto/hmac" + "crypto/sha256" + "crypto/rand" ) -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: - switch e.m.typeid { - case MSG_VIDEO: - return fmt.Sprintf("ts %d video %d bytes key %t", e.m.curts, e.m.data.Len(), e.m.key) - case MSG_AUDIO: - return fmt.Sprintf("ts %d audio %d bytes", e.m.curts, e.m.data.Len()) - } - case E_CLOSE: - return "close" +func ParseURL(uri string) (host string, path string) { + var u *url.URL + if u, _ = url.Parse(uri); u == nil { + return } - return "" + host = u.Host + if _, _, err := net.SplitHostPort(u.Host); err != nil { + host += ":1935" + } + path = u.Path + return } -/* -server: - connect - createStream - publish -client: - connect - createStream - getStreamLength - play -*/ +func DialTimeout(uri string, timeout time.Duration) (conn *Conn, err error) { + host, path := ParseURL(uri) + dailer := net.Dialer{Timeout: timeout} + var netconn net.Conn + if netconn, err = dailer.Dial("tcp", host); err != nil { + return + } + + fmt.Println("rtmp: connected") + + conn = NewConn(netconn) + conn.Host = host + conn.Path = path + return +} + +type Server struct { + Addr string + HandlePublish func(*Conn) + HandlePlay func(*Conn) +} + +func (self *Server) handleConn(conn *Conn) (err error) { + if err = conn.handshake(); err != nil { + return + } + + if err = conn.determineType(); err != nil { + fmt.Println("rtmp: conn closed:", err) + return + } + + if conn.playing { + if self.HandlePlay != nil { + self.HandlePlay(conn) + conn.Close() + } + } + + return +} + +func (self *Server) ListenAndServe() (err error) { + addr := self.Addr + if addr == "" { + addr = ":1935" + } + var tcpaddr *net.TCPAddr + if tcpaddr, err = net.ResolveTCPAddr("tcp", addr); err != nil { + err = fmt.Errorf("rtmp: ListenAndServe: %s", err) + return + } + + var listener *net.TCPListener + if listener, err = net.ListenTCP("tcp", tcpaddr); err != nil { + return + } + + var netconn net.Conn + for { + if netconn, err = listener.Accept(); err != nil { + return + } + + conn := NewConn(netconn) + conn.isserver = true + go self.handleConn(conn) + } +} + +type Conn struct { + Host string + Path string + Debug bool + streams []av.CodecData + + br *pio.Reader + bw *pio.Writer + bufr *bufio.Reader + bufw *bufio.Writer + intw *pio.Writer + netconn net.Conn + + writeMaxChunkSize int + readMaxChunkSize int + + lastcsid uint32 + lastcs *chunkStream + csmap map[uint32]*chunkStream + + isserver bool + publishing, playing bool + reading, writing bool + avmsgsid uint32 + + gotcommand bool + commandname string + commandtransid float64 + commandobj flvio.AMFMap + commandparams []interface{} + + gotmsg bool + msgdata []byte + msgtypeid uint8 + + eventtype uint16 +} + +func NewConn(netconn net.Conn) *Conn { + conn := &Conn{} + conn.netconn = netconn + conn.csmap = make(map[uint32]*chunkStream) + conn.readMaxChunkSize = 128 + conn.writeMaxChunkSize = 128 + conn.bufr = bufio.NewReaderSize(netconn, 4096) + conn.bufw = bufio.NewWriterSize(netconn, 4096) + conn.br = pio.NewReader(conn.bufr) + conn.bw = pio.NewWriter(conn.bufw) + conn.intw = pio.NewWriter(nil) + return conn +} + +type chunkStream struct { + timenow uint32 + timedelta uint32 + hastimeext bool + msgsid uint32 + msgtypeid uint8 + msgdatalen uint32 + msgdataleft uint32 + msghdrtype uint8 + msgdata []byte +} + +func (self *chunkStream) Start() { + self.msgdataleft = self.msgdatalen + self.msgdata = make([]byte, self.msgdatalen) +} const ( - E_NEW = iota - E_PUBLISH - E_PLAY - E_DATA - E_CLOSE + msgtypeidUserControl = 4 + msgtypeidWindowAckSize = 5 + msgtypeidSetPeerBandwidth = 6 + msgtypeidSetChunkSize = 1 + msgtypeidCommandMsgAMF0 = 20 + msgtypeidCommandMsgAMF3 = 17 + msgtypeidDataMsgAMF0 = 18 + msgtypeidDataMsgAMF3 = 15 + msgtypeidVideoMsg = 9 + msgtypeidAudioMsg = 8 ) -func handleConnect(mr *MsgStream, trans float64, app string) { +const ( + eventtypeStreamBegin = 0 + eventtypeSetBufferLength = 3 +) - 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 (self *Conn) Close() (err error) { + return self.netconn.Close() } -func handleMeta(mr *MsgStream, obj AMFObj) { - - mr.meta = obj - 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 (self *Conn) pollCommand() (err error) { + for { + if err = self.pollMsg(); err != nil { + return + } + if self.gotcommand { + return + } + } } -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 (self *Conn) pollMsg() (err error) { + self.gotmsg = false + self.gotcommand = false + for { + if err = self.readChunk(); err != nil { + return + } + if self.gotmsg { + fmt.Println("rtmp: gotmsg iscommand", self.gotcommand) + return + } + } } -func handleGetStreamLength(mr *MsgStream, trans float64) { -} +func (self *Conn) determineType() (err error) { + var connectpath, playpath string -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 { + // < connect("app") + if err = self.pollCommand(); 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)) + if self.commandname != "connect" { + err = fmt.Errorf("rtmp: first command is not connect") + return } - 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) + if self.commandobj != nil { + app := self.commandobj["app"] + connectpath, _ = app.(string) } - begin := func () { - - 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: begin: video %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.meta.obj["Server"] = AMFObj { atype : AMF_STRING, str : "Golang Rtmp Server", } - mr.meta.atype = AMF_OBJECT - l.Printf("stream %v: %v", mr, mr.meta) - mr.WriteAMFMeta(5, strid, []AMFObj { - AMFObj { atype : AMF_STRING, str : "onMetaData", }, - mr.meta, - /* - 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, }, - }, - }, - */ - }) + // > WindowAckSize + if err = self.writeWindowAckSize(5000000); err != nil { + return + } + // > SetPeerBandwidth + if err = self.writeSetPeerBandwidth(5000000, 2); err != nil { + return + } + self.writeMaxChunkSize = 4096 + // > SetChunkSize + if err = self.writeSetChunkSize(uint32(self.writeMaxChunkSize)); err != nil { + return } - end := func () { - - l.Printf("stream %v: end", mr) - - var b bytes.Buffer - WriteInt(&b, 1, 2) - WriteInt(&b, strid, 4) - mr.WriteMsg(0, 2, MSG_USER, 0, 0, b.Bytes()) // stream eof 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.Stop", }, - "description" : AMFObj { atype : AMF_STRING, str : "Stop live.", }, - }, - }, - }) - } - - if tsrc == nil { - - for { - nr := 0 - - for { - m := <-mr.que - if m == nil { - break - } - //if nr == 0 && !m.key { - // continue - //} - if nr == 0 { - begin() - 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("data %v: got %v curts %v", mr, m, m.curts) - 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:]) - } - nr++ - } - end() - } - - } else { - - begin() - - 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) - //if err != "EOF" { - // l.Printf("stream %v: %v", mr, string(debug.Stack())) - //} - } - }() - - handShake(mr.r) - -// f, _ := os.Create("/tmp/pub.log") -// mr.l = log.New(f, "", 0) + // > _result("NetConnection.Connect.Success") + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "_result") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "fmtVer": "FMS/3,0,1,123", + "capabilities": 31, + }) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "level": "status", + "code": "NetConnection.Connect.Success", + "description": "Connection Success.", + "objectEncoding": 3, + }) + self.writeCommandMsgEnd(3, 0) for { - m := mr.ReadMsg() - if m == nil { - continue + if err = self.pollMsg(); err != nil { + return } + if self.gotcommand { + switch self.commandname { - //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) + // < createStream case "createStream": - a2 := ReadAMF(m.data) - handleCreateStream(mr, a2.f64) - case "publish": - handlePublish(mr) + self.avmsgsid = uint32(1) + // > _result(streamid) + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "_result") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, self.avmsgsid) // streamid=1 + self.writeCommandMsgEnd(3, 0) + + // < play("path") case "play": - handlePlay(mr, m.strid) + if len(self.commandparams) < 1 { + err = fmt.Errorf("rtmp: play params invalid") + return + } + playpath, _ = self.commandparams[0].(string) + + // > streamBegin(streamid) + self.writeStreamBegin(self.avmsgsid) + + // > onStatus() + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "onStatus") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "level": "status", + "code": "NetStream.Play.Start", + "description": "Start live", + }) + self.writeCommandMsgEnd(5, self.avmsgsid) + + // > |RtmpSampleAccess() + w = self.writeDataMsgStart() + flvio.WriteAMF0Val(w, "|RtmpSampleAccess") + flvio.WriteAMF0Val(w, true) + flvio.WriteAMF0Val(w, true) + self.writeDataMsgEnd(5, self.avmsgsid) + + fmt.Println("rtmp: playing") + + self.Path = fmt.Sprintf("/%s/%s", connectpath, playpath) + self.playing = true + self.writing = true + return } + } } + + return } -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: - e.mr.role = PLAYER - e.mr.que = make(chan *Msg, 16) - for _, mr := range idmap { - if mr.role == PUBLISHER && mr.app == e.mr.app && mr.stat == WAIT_DATA { - e.mr.W = mr.W - e.mr.H = mr.H - e.mr.extraA = mr.extraA - e.mr.extraV = mr.extraV - e.mr.meta = mr.meta - } - } - case e.id == E_CLOSE: - if e.mr.role == PUBLISHER { - delete(pubmap, e.mr.app) - for _, mr := range idmap { - if mr.role == PLAYER && mr.app == e.mr.app { - ch := reflect.ValueOf(mr.que) - var m *Msg = nil - ch.TrySend(reflect.ValueOf(m)) - } - } - } - 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 - for _, mr := range idmap { - if mr.role == PLAYER && mr.app == e.mr.app { - mr.W = e.mr.W - mr.H = e.mr.H - mr.extraA = e.mr.extraA - mr.extraV = e.mr.extraV - mr.meta = e.mr.meta - } - } - } - 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) +func (self *Conn) checkConnectResult() (ok bool, errmsg string) { + errmsg = "params invalid" + if len(self.commandparams) < 1 { return } - go listenEvent() + + obj, _ := self.commandparams[0].(flvio.AMFMap) + if obj == nil { + return + } + + _code, _ := obj["code"] + if _code == nil { + return + } + code, _ := _code.(string) + if code != "NetConnection.Connect.Success" { + return + } + + ok = true + return +} + +func (self *Conn) checkCreateStreamResult() (ok bool, avmsgsid uint32) { + if len(self.commandparams) < 1 { + return + } + + ok = true + _avmsgsid, _ := self.commandparams[0].(float64) + avmsgsid = uint32(_avmsgsid) + return +} + +func (self *Conn) connectPlay() (err error) { + var connectpath, playpath string + pathsegs := strings.Split(self.Path, "/") + if len(pathsegs) > 1 { + connectpath = pathsegs[1] + } + if len(pathsegs) > 2 { + playpath = pathsegs[2] + } + + // > connect("app") + fmt.Printf("rtmp: > connect('%s') host=%s\n", connectpath, self.Host) + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "connect") + flvio.WriteAMF0Val(w, 1) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "app": connectpath, + "flashVer": "MAC 22,0,0,192", + "tcUrl": fmt.Sprintf("rtmp://%s/%s", self.Host, connectpath), + "fpad": false, + "capabilities": 15, + "audioCodecs": 4071, + "videoCodecs": 252, + "videoFunction": 1, + }) + self.writeCommandMsgEnd(3, 0) + for { - c, err := ln.Accept() - if err != nil { - l.Printf("server: error: sock accept %s\n", err) + if err = self.pollMsg(); err != nil { + return + } + if self.gotcommand { + // < _result("NetConnection.Connect.Success") + if self.commandname == "_result" { + var ok bool + var errmsg string + if ok, errmsg = self.checkConnectResult(); !ok { + err = fmt.Errorf("rtmp: command connect failed: %s", errmsg) + return + } + fmt.Printf("rtmp: < _result() of connect\n") + break + } + } else { + if self.msgtypeid == msgtypeidWindowAckSize { + self.writeWindowAckSize(2500000) + } + } + } + + // > createStream() + fmt.Printf("rtmp: > createStream()\n") + w = self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "createStream") + flvio.WriteAMF0Val(w, 2) + flvio.WriteAMF0Val(w, nil) + self.writeCommandMsgEnd(3, 0) + + // > SetBufferLength 0,3000ms + self.writeSetBufferLength(0, 3000) + + for { + if err = self.pollMsg(); err != nil { + return + } + if self.gotcommand { + // < _result(avmsgsid) of createStream + if self.commandname == "_result" { + var ok bool + if ok, self.avmsgsid = self.checkCreateStreamResult(); !ok { + err = fmt.Errorf("rtmp: createStream command failed") + return + } + break + } + } + } + + fmt.Println("rtmp: play", playpath) + + return +} + +func (self *Conn) ReadHeader() (err error) { + if !self.reading && !self.writing { + if err = self.handshake(); err != nil { + return + } + if err = self.connectPlay(); err != nil { + return + } + } + return +} + +func (self *Conn) Streams() (streams []av.CodecData, err error) { + streams = self.streams + return +} + +func (self *Conn) WritePacket(pkt av.Packet) (err error) { + ts := uint32(pkt.Time/time.Millisecond) + stream := self.streams[pkt.Idx] + fmt.Println("rtmp: WritePacket", pkt.Idx, pkt.Time, pkt.CompositionTime, ts) + + switch stream.Type() { + case av.AAC: + audiodata := self.makeAACAudiodata(stream.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) + w := self.writeAudioDataStart() + audiodata.Marshal(w) + self.writeAudioDataEnd(ts) + + case av.H264: + videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.IsKeyFrame, pkt.Data) + videodata.CompositionTime = int32(pkt.CompositionTime/time.Millisecond) + w := self.writeVideoDataStart() + videodata.Marshal(w) + self.writeVideoDataEnd(ts) + } + + return +} + +func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { + metadata := flvio.AMFMap{} + + metadata["server"] = "joy4 streaming server (github.com/nareix/joy4)" + metadata["duration"] = 0 + + for _, _stream := range streams { + typ := _stream.Type() + switch { + case typ.IsVideo(): + stream := _stream.(av.VideoCodecData) + switch typ { + case av.H264: + metadata["videocodecid"] = flvio.VIDEO_H264 + + default: + err = fmt.Errorf("rtmp: WriteHeader: unsupported video codecType=%v", stream.Type()) + return + } + + metadata["width"] = stream.Width() + metadata["height"] = stream.Height() + metadata["framerate"] = 24 // TODO: make it correct + metadata["videodatarate"] = 0 + + case typ.IsAudio(): + stream := _stream.(av.AudioCodecData) + switch typ { + case av.AAC: + metadata["audiocodecid"] = flvio.SOUND_AAC + + default: + err = fmt.Errorf("rtmp: WriteHeader: unsupported audio codecType=%v", stream.Type()) + return + } + + metadata["audiodatarate"] = 0 + } + } + + // > onMetaData() + w := self.writeDataMsgStart() + flvio.WriteAMF0Val(w, "onMetaData") + flvio.WriteAMF0Val(w, metadata) + if err = self.writeDataMsgEnd(5, self.avmsgsid); err != nil { + return + } + + // > Videodata(decoder config) + // > Audiodata(decoder config) + for _, stream := range streams { + switch stream.Type() { + case av.H264: + h264 := stream.(h264parser.CodecData) + videodata := self.makeH264Videodata(flvio.AVC_SEQHDR, true, h264.AVCDecoderConfRecordBytes()) + w := self.writeVideoDataStart() + videodata.Marshal(w) + if err = self.writeVideoDataEnd(0); err != nil { + return + } + + case av.AAC: + aac := stream.(aacparser.CodecData) + audiodata := self.makeAACAudiodata(aac, flvio.AAC_SEQHDR, aac.MPEG4AudioConfigBytes()) + w := self.writeAudioDataStart() + audiodata.Marshal(w) + if err = self.writeAudioDataEnd(0); err != nil { + return + } + } + } + + self.streams = streams + return +} + +func (self *Conn) makeH264Videodata(pkttype uint8, iskeyframe bool, data []byte) flvio.Videodata { + videodata := flvio.Videodata{ + CodecID: flvio.VIDEO_H264, + AVCPacketType: pkttype, + Data: data, + } + if iskeyframe { + videodata.FrameType = flvio.FRAME_KEY + } else { + videodata.FrameType = flvio.FRAME_INTER + } + return videodata +} + +func (self *Conn) makeAACAudiodata(stream av.AudioCodecData, pkttype uint8, data []byte) flvio.Audiodata { + audiodata := flvio.Audiodata{ + SoundFormat: flvio.SOUND_AAC, + SoundRate: flvio.SOUND_44Khz, + AACPacketType: pkttype, + Data: data, + } + switch stream.SampleFormat().BytesPerSample() { + case 1: + audiodata.SoundSize = flvio.SOUND_8BIT + default: + audiodata.SoundSize = flvio.SOUND_16BIT + } + switch stream.ChannelLayout().Count() { + case 1: + audiodata.SoundType = flvio.SOUND_MONO + case 2: + audiodata.SoundType = flvio.SOUND_STEREO + } + return audiodata +} + +func (self *Conn) writeSetChunkSize(size uint32) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(size) + return self.writeProtoCtrlMsgEnd(msgtypeidSetChunkSize) +} + +func (self *Conn) writeWindowAckSize(size uint32) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(size) + return self.writeProtoCtrlMsgEnd(msgtypeidWindowAckSize) +} + +func (self *Conn) writeSetPeerBandwidth(acksize uint32, limittype uint8) (err error) { + w := self.writeProtoCtrlMsgStart() + w.WriteU32BE(acksize) + w.WriteU8(limittype) + return self.writeProtoCtrlMsgEnd(msgtypeidSetPeerBandwidth) +} + +func (self *Conn) writeProtoCtrlMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeProtoCtrlMsgEnd(msgtypeid uint8) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(2, 0, msgtypeid, 0, msgdatav) +} + +func (self *Conn) writeCommandMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeCommandMsgEnd(csid uint32, msgsid uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(csid, 0, msgtypeidCommandMsgAMF0, msgsid, msgdatav) +} + +func (self *Conn) writeDataMsgStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeDataMsgEnd(csid uint32, msgsid uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(csid, 0, msgtypeidDataMsgAMF0, msgsid, msgdatav) +} + +func (self *Conn) writeVideoDataStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeVideoDataEnd(timestamp uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(7, timestamp, msgtypeidVideoMsg, self.avmsgsid, msgdatav) +} + +func (self *Conn) writeAudioDataStart() *pio.Writer { + self.intw.SaveToVecOn() + return self.intw +} + +func (self *Conn) writeAudioDataEnd(timestamp uint32) (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(6, timestamp, msgtypeidAudioMsg, self.avmsgsid, msgdatav) +} + +func (self *Conn) writeUserControlMsgStart(eventtype uint16) *pio.Writer { + self.intw.SaveToVecOn() + self.intw.WriteU16BE(eventtype) + return self.intw +} + +func (self *Conn) writeUserControlMsgEnd() (err error) { + msgdatav := self.intw.SaveToVecOff() + return self.writeChunks(2, 0, msgtypeidUserControl, 0, msgdatav) +} + +func (self *Conn) writeStreamBegin(msgsid uint32) (err error) { + w := self.writeUserControlMsgStart(eventtypeStreamBegin) + w.WriteU32BE(msgsid) + return self.writeUserControlMsgEnd() +} + +func (self *Conn) writeSetBufferLength(msgsid uint32, timestamp uint32) (err error) { + w := self.writeUserControlMsgStart(eventtypeStreamBegin) + w.WriteU32BE(msgsid) + w.WriteU32BE(timestamp) + return self.writeUserControlMsgEnd() +} + +func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, msgsid uint32, msgdatav [][]byte) (err error) { + msgdatalen := pio.VecLen(msgdatav) + + // [Type 0][Type 3][Type 3].... + + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| msg stream id | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message stream id (cont) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 9 Chunk Message Header – Type 0 + if err = self.bw.WriteU8(byte(csid)&0x3f); err != nil { + return + } + if err = self.bw.WriteU24BE(timestamp); err != nil { + return + } + if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { + return + } + if err = self.bw.WriteU8(msgtypeid); err != nil { + return + } + if err = self.bw.WriteU32LE(msgsid); err != nil { + return + } + + msgdataoff := 0 + for { + size := msgdatalen - msgdataoff + if size > self.writeMaxChunkSize { + size = self.writeMaxChunkSize + } + + write := pio.VecSlice(msgdatav, msgdataoff, msgdataoff+size) + for _, b := range write { + if _, err = self.bw.Write(b); err != nil { + return + } + } + + msgdataoff += size + if msgdataoff == msgdatalen { break } - go func (c net.Conn) { - mr := NewMsgStream(c) - event <- eventS{id:E_NEW, mr:mr} - <-eventDone - serve(mr) - } (c) + + // Type 3 + if err = self.bw.WriteU8(byte(csid)&0x3f|3<<6); err != nil { + return + } + } + + fmt.Printf("rtmp: write chunk msgdatalen=%d msgsid=%d\n", msgdatalen, msgsid) + + if err = self.bufw.Flush(); err != nil { + return + } + + return +} + +func (self *Conn) readChunk() (err error) { + var msghdrtype uint8 + var csid uint32 + var header uint8 + if header, err = self.br.ReadU8(); err != nil { + return + } + msghdrtype = header>>6 + + csid = uint32(header)&0x3f + switch csid { + default: // Chunk basic header 1 + case 0: // Chunk basic header 2 + var i uint8 + if i, err = self.br.ReadU8(); err != nil { + return + } + csid = uint32(i)+64 + case 1: // Chunk basic header 3 + var i uint16 + if i, err = self.br.ReadU16BE(); err != nil { + return + } + csid = uint32(i)+64 + } + + var cs *chunkStream + if self.lastcs != nil && self.lastcsid == csid { + cs = self.lastcs + } else { + cs = &chunkStream{} + self.csmap[csid] = cs + } + self.lastcs = cs + self.lastcsid = csid + + var timestamp uint32 + + switch msghdrtype { + case 0: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| msg stream id | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message stream id (cont) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 9 Chunk Message Header – Type 0 + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(11); err != nil { + return + } + timestamp = pio.GetU24BE(h[0:3]) + cs.msghdrtype = msghdrtype + cs.msgdatalen = pio.GetU24BE(h[3:6]) + cs.msgtypeid = h[6] + cs.msgsid = pio.GetU32LE(h[7:11]) + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.hastimeext = true + } else { + cs.hastimeext = false + } + cs.timenow = timestamp + cs.Start() + + case 1: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 10 Chunk Message Header – Type 1 + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(7); err != nil { + return + } + timestamp = pio.GetU24BE(h[0:3]) + cs.msghdrtype = msghdrtype + cs.msgdatalen = pio.GetU24BE(h[3:6]) + cs.msgtypeid = h[6] + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.hastimeext = true + } else { + cs.hastimeext = false + } + cs.timedelta = timestamp + cs.timenow += timestamp + cs.Start() + + case 2: + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 11 Chunk Message Header – Type 2 + if cs.msgdataleft != 0 { + err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) + return + } + var h[]byte + if h, err = self.br.ReadBytes(3); err != nil { + return + } + cs.msghdrtype = msghdrtype + timestamp = pio.GetU24BE(h[0:3]) + if timestamp == 0xffffff { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.hastimeext = true + } else { + cs.hastimeext = false + } + cs.timedelta = timestamp + cs.timenow += timestamp + cs.Start() + + case 3: + if cs.msgdataleft == 0 { + switch cs.msghdrtype { + case 0: + if cs.hastimeext { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + cs.timenow = timestamp + } + case 1, 2: + if cs.hastimeext { + if timestamp, err = self.br.ReadU32BE(); err != nil { + return + } + } else { + timestamp = cs.timedelta + } + cs.timenow += timestamp + } + cs.Start() + } + + default: + err = fmt.Errorf("rtmp: invalid chunk msg header type=%d", msghdrtype) + return + } + + size := int(cs.msgdataleft) + if size > self.readMaxChunkSize { + size = self.readMaxChunkSize + } + off := cs.msgdatalen-cs.msgdataleft + buf := cs.msgdata[off:int(off)+size] + if _, err = io.ReadFull(self.br, buf); err != nil { + return + } + cs.msgdataleft -= uint32(size) + + if true { + fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", + csid, cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) + } + + if cs.msgdataleft == 0 { + if true { + fmt.Println("rtmp: chunk data") + fmt.Print(hex.Dump(cs.msgdata)) + fmt.Printf("%x\n", cs.msgdata) + } + + if err = self.handleMsg(cs.msgsid, cs.msgtypeid, cs.msgdata); err != nil { + return + } + } + + return +} + +func (self *Conn) handleCommandMsgAMF0(r *pio.Reader) (err error) { + commandname, _ := flvio.ReadAMF0Val(r) + commandtransid, _ := flvio.ReadAMF0Val(r) + commandobj, _ := flvio.ReadAMF0Val(r) + + var ok bool + if self.commandname, ok = commandname.(string); !ok { + err = fmt.Errorf("rtmp: CommandMsgAMF0 command is not string") + return + } + + self.commandobj, _ = commandobj.(flvio.AMFMap) + self.commandtransid, _ = commandtransid.(float64) + self.commandparams = []interface{}{} + for { + if val, rerr := flvio.ReadAMF0Val(r); rerr != nil { + break + } else { + self.commandparams = append(self.commandparams, val) + } + } + + self.gotcommand = true + return +} + +func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err error) { + self.msgdata = msgdata + self.msgtypeid = msgtypeid + + switch msgtypeid { + case msgtypeidCommandMsgAMF0: + r := pio.NewReaderBytes(msgdata) + if err = self.handleCommandMsgAMF0(r); err != nil { + return + } + + case msgtypeidCommandMsgAMF3: + r := pio.NewReaderBytes(msgdata) + r.ReadU8() // skip first byte + if err = self.handleCommandMsgAMF0(r); err != nil { + return + } + + case msgtypeidUserControl: + if len(msgdata) >= 2 { + self.eventtype = pio.GetU16BE(msgdata) + } else { + err = fmt.Errorf("rtmp: short packet of UserControl") + return + } + } + + self.gotmsg = true + return +} + +var ( + hsClientFullKey = []byte{ + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', + '0', '0', '1', + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, + } + hsServerFullKey = []byte{ + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', + 'S', 'e', 'r', 'v', 'e', 'r', ' ', + '0', '0', '1', + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, + } + hsClientPartialKey = hsClientFullKey[:30] + hsServerPartialKey = hsServerFullKey[:36] +) + +func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) { + h := hmac.New(sha256.New, key) + if gap <= 0 { + h.Write(src) + } else { + h.Write(src[:gap]) + h.Write(src[gap+32:]) + } + return h.Sum(nil) +} + +func hsCalcDigestPos(p []byte, off int, mod int, add int) (pos int) { + for i := 0; i < 4; i++ { + pos += int(p[i+off]) + } + pos = pos%mod+add + return +} + +func hsCreateC1(p []byte) { + p[4] = 9 + p[5] = 0 + p[6] = 124 + p[7] = 2 + rand.Read(p[8:]) + gap := hsCalcDigestPos(p, 8, 728, 12) + digest := hsMakeDigest(hsClientPartialKey, p, gap) + copy(p[gap:], digest) +} + +func (self *Conn) handshake() (err error) { + if self.isserver { + return self.handshakeServer() + } else { + return self.handshakeClient() } } +func (self *Conn) handshakeClient() (err error) { + var random [(1+1536*2)*2]byte + + C0C1C2 := random[:1536*2+1] + C0 := C0C1C2[:1] + C1 := C0C1C2[1:1536+1] + C0C1 := C0C1C2[:1536+1] + C2 := C0C1C2[1536+1:] + + S0S1S2 := random[1536*2+1:] + //S0 := S0S1S2[:1] + S1 := S0S1S2[1:1536+1] + //S0S1 := S0S1S2[:1536+1] + //S2 := S0S1S2[1536+1:] + + C0[0] = 3 + hsCreateC1(C1) + + // > C0C1 + if _, err = self.bw.Write(C0C1); err != nil { + return + } + if err = self.bufw.Flush(); err != nil { + return + } + + // < S0S1S2 + if _, err = io.ReadFull(self.br, S0S1S2); err != nil { + return + } + + fmt.Println("rtmp: handshakeClient: server version", S1[4],S1[5],S1[6],S1[7]) + + if S1[4] >= 3 { + } else { + C2 = S1 + } + + // > C2 + if _, err = self.bw.Write(C2); err != nil { + return + } + if err = self.bufw.Flush(); err != nil { + return + } + + return +} + +func (self *Conn) handshakeServer() (err error) { + return +} + From 7d9968dd5d64d6f6166eb3e5814ae2e5aae3123f Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 19:38:19 +0800 Subject: [PATCH 09/24] remove old codes --- amf.go | 100 ------ amf_test.go | 21 -- handshake.go | 146 -------- msg.go | 300 ---------------- new.go | 981 --------------------------------------------------- util.go | 113 ------ 6 files changed, 1661 deletions(-) delete mode 100644 amf.go delete mode 100644 amf_test.go delete mode 100644 handshake.go delete mode 100644 msg.go delete mode 100644 new.go delete mode 100644 util.go diff --git a/amf.go b/amf.go deleted file mode 100644 index cd465ba..0000000 --- a/amf.go +++ /dev/null @@ -1,100 +0,0 @@ - -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) - } -} - diff --git a/amf_test.go b/amf_test.go deleted file mode 100644 index 57b59d3..0000000 --- a/amf_test.go +++ /dev/null @@ -1,21 +0,0 @@ - -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) -} - diff --git a/handshake.go b/handshake.go deleted file mode 100644 index 31911b3..0000000 --- a/handshake.go +++ /dev/null @@ -1,146 +0,0 @@ - -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") - err = 1 - return - } - - epoch := b[1:5] - ver := b[5:9] - l.Printf("handshake: epoch %v ver %v", epoch, ver) - - // random - 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") - err = 1 - return - } - } - - l.Printf("handshake: offs = %v", offs) - - dig = makeDigest(key, b[1+offs:1+offs+32], -1) - return -} - - -func handShake(rw io.ReadWriter) { - b := ReadBuf(rw, 1537) // C0+C1 - l.Printf("handshake: got client chal") - dig, err := parseChal(b, clientKey2, serverKey) - if err != 0 { - return - } - - createChal(b, serverVersion, serverKey2) - l.Printf("handshake: send server chal") - rw.Write(b) // S0+S1 - - b = make([]byte, 1536) - createResp(b, dig) - l.Printf("handshake: send server resp") - rw.Write(b) // S2 - - b = ReadBuf(rw, 1536) // C2 - l.Printf("handshake: got client resp") -} - diff --git a/msg.go b/msg.go deleted file mode 100644 index 011bdc6..0000000 --- a/msg.go +++ /dev/null @@ -1,300 +0,0 @@ - -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 - - meta AMFObj - 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 -} - diff --git a/new.go b/new.go deleted file mode 100644 index eb25ad4..0000000 --- a/new.go +++ /dev/null @@ -1,981 +0,0 @@ - -package rtmp - -import ( - "bytes" - "net" - "bufio" - "time" - "fmt" - "encoding/hex" - "io" - "github.com/nareix/pio" - "github.com/nareix/flv/flvio" - "github.com/nareix/av" - "github.com/nareix/codec/h264parser" - "github.com/nareix/codec/aacparser" - "crypto/hmac" - "crypto/sha256" - "crypto/rand" -) - -type Server struct { - Addr string - HandlePublish func(*Conn) - HandlePlay func(*Conn) -} - -func (self *Server) handleConn(conn *Conn) (err error) { - if err = conn.determineType(); err != nil { - fmt.Println("rtmp: conn closed:", err) - return - } - - if conn.playing { - if self.HandlePlay != nil { - self.HandlePlay(conn) - conn.Close() - } - } - - return -} - -func (self *Server) ListenAndServe() (err error) { - addr := self.Addr - if addr == "" { - addr = ":1935" - } - var tcpaddr *net.TCPAddr - if tcpaddr, err = net.ResolveTCPAddr("tcp", addr); err != nil { - err = fmt.Errorf("rtmp: ListenAndServe: %s", err) - return - } - - var listener *net.TCPListener - if listener, err = net.ListenTCP("tcp", tcpaddr); err != nil { - return - } - - var netconn net.Conn - for { - if netconn, err = listener.Accept(); err != nil { - return - } - - conn := newConn(netconn) - go self.handleConn(conn) - } -} - -type Conn struct { - RequestUri string - streams []av.CodecData - - br *pio.Reader - bw *pio.Writer - bufr *bufio.Reader - bufw *bufio.Writer - intw *pio.Writer - netconn net.Conn - - writeMaxChunkSize int - readMaxChunkSize int - - lastcsid uint32 - lastcs *chunkStream - csmap map[uint32]*chunkStream - - publishing, playing bool - playmsgcsid uint32 - - gotcommand bool - commandname string - commandtransid float64 - commandobj flvio.AMFMap - commandparams []interface{} - - gotmsg bool - msgdata []byte - msgtypeid uint8 - msgcsid uint32 - - eventtype uint16 -} - -func newConn(netconn net.Conn) *Conn { - conn := &Conn{} - conn.netconn = netconn - conn.csmap = make(map[uint32]*chunkStream) - conn.readMaxChunkSize = 128 - conn.writeMaxChunkSize = 128 - conn.bufr = bufio.NewReaderSize(netconn, 512) - conn.bufw = bufio.NewWriterSize(netconn, 512) - conn.br = pio.NewReader(conn.bufr) - conn.bw = pio.NewWriter(conn.bufw) - conn.intw = pio.NewWriter(nil) - return conn -} - -type chunkStream struct { - timenow uint32 - timedelta uint32 - hastimeext bool - msgsid uint32 - msgtypeid uint8 - msgdatalen uint32 - msgdataleft uint32 - msghdrtype uint8 - msgdata []byte -} - -func (self *chunkStream) Start() { - self.msgdataleft = self.msgdatalen - self.msgdata = make([]byte, self.msgdatalen) -} - -const ( - msgtypeidUserControl = 4 - msgtypeidWindowAckSize = 5 - msgtypeidSetPeerBandwidth = 6 - msgtypeidSetChunkSize = 1 - msgtypeidCommandMsgAMF0 = 20 - msgtypeidCommandMsgAMF3 = 17 - msgtypeidDataMsgAMF0 = 18 - msgtypeidDataMsgAMF3 = 15 - msgtypeidVideoMsg = 9 - msgtypeidAudioMsg = 8 -) - -const ( - eventtypeStreamBegin = 0 -) - -func (self *Conn) Close() (err error) { - return self.netconn.Close() -} - -func (self *Conn) pollCommand() (err error) { - for { - if err = self.pollMsg(); err != nil { - return - } - if self.gotcommand { - return - } - } -} - -func (self *Conn) pollMsg() (err error) { - self.gotmsg = false - self.gotcommand = false - for { - if err = self.readChunk(); err != nil { - return - } - if self.gotmsg { - fmt.Println("rtmp: gotmsg iscommand", self.gotcommand) - return - } - } -} - -func (self *Conn) determineType() (err error) { - if err = self.handshake(); err != nil { - return - } - - // < connect - if err = self.pollCommand(); err != nil { - return - } - if self.commandname != "connect" { - err = fmt.Errorf("rtmp: first command is not connect") - return - } - - // > WindowAckSize - if err = self.writeWindowAckSize(5000000); err != nil { - return - } - // > SetPeerBandwidth - if err = self.writeSetPeerBandwidth(5000000, 2); err != nil { - return - } - // > SetChunkSize - if err = self.writeSetChunkSize(uint32(self.writeMaxChunkSize)); err != nil { - return - } - - // > _result("NetConnection.Connect.Success") - w := self.writeCommandMsgStart() - flvio.WriteAMF0Val(w, "_result") - flvio.WriteAMF0Val(w, self.commandtransid) - flvio.WriteAMF0Val(w, flvio.AMFMap{ - "fmtVer": "FMS/3,0,1,123", - "capabilities": 31, - }) - flvio.WriteAMF0Val(w, flvio.AMFMap{ - "level": "status", - "code": "NetConnection.Connect.Success", - "description": "Connection Success.", - "objectEncoding": 3, - }) - self.writeCommandMsgEnd(3, 0) - - for { - if err = self.pollMsg(); err != nil { - return - } - if self.gotcommand { - switch self.commandname { - - // < createStream - case "createStream": - self.playmsgcsid = uint32(1) - // > _result(streamid) - w := self.writeCommandMsgStart() - flvio.WriteAMF0Val(w, "_result") - flvio.WriteAMF0Val(w, self.commandtransid) - flvio.WriteAMF0Val(w, nil) - flvio.WriteAMF0Val(w, self.playmsgcsid) // streamid=1 - self.writeCommandMsgEnd(3, 0) - - // < play("path") - case "play": - if len(self.commandparams) < 1 { - err = fmt.Errorf("rtmp: play params invalid") - return - } - path, _ := self.commandparams[0].(string) - self.RequestUri = path - fmt.Println("rtmp: play", path) - - // > streamBegin(streamid) - self.writeStreamBegin(self.playmsgcsid) - - // > onStatus() - w := self.writeCommandMsgStart() - flvio.WriteAMF0Val(w, "onStatus") - flvio.WriteAMF0Val(w, self.commandtransid) - flvio.WriteAMF0Val(w, nil) - flvio.WriteAMF0Val(w, flvio.AMFMap{ - "level": "status", - "code": "NetStream.Play.Start", - "description": "Start live", - }) - self.writeCommandMsgEnd(5, self.playmsgcsid) - - // > |RtmpSampleAccess() - w = self.writeDataMsgStart() - flvio.WriteAMF0Val(w, "|RtmpSampleAccess") - flvio.WriteAMF0Val(w, true) - flvio.WriteAMF0Val(w, true) - self.writeDataMsgEnd(5, self.playmsgcsid) - - fmt.Println("rtmp: playing") - self.playing = true - return - } - - } - } - - return -} - -func (self *Conn) WritePacket(pkt av.Packet) (err error) { - ts := uint32(pkt.Time/time.Millisecond) - stream := self.streams[pkt.Idx] - - switch stream.Type() { - case av.AAC: - audiodata := self.makeAACAudiodata(stream.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) - w := self.writeAudioDataStart() - audiodata.Marshal(w) - self.writeAudioDataEnd(ts) - - case av.H264: - videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.Data) - w := self.writeVideoDataStart() - videodata.Marshal(w) - self.writeVideoDataEnd(ts) - } - return -} - -func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { - metadata := flvio.AMFMap{} - metadata["Server"] = "joy4" - metadata["duration"] = 0 - - for _, _stream := range streams { - typ := _stream.Type() - switch { - case typ.IsVideo(): - stream := _stream.(av.VideoCodecData) - switch typ { - case av.H264: - metadata["videocodecid"] = flvio.VIDEO_H264 - - default: - err = fmt.Errorf("rtmp: WriteHeader unsupported video codecType=%v", stream.Type()) - return - } - - metadata["width"] = stream.Width() - metadata["height"] = stream.Height() - metadata["displayWidth"] = stream.Width() - metadata["displayHeight"] = stream.Height() - metadata["framerate"] = 24 // TODO: make it correct - metadata["fps"] = 24 - metadata["videodatarate"] = 1538 // TODO: make it correct - metadata["profile"] = "" - metadata["level"] = "" - - case typ.IsAudio(): - stream := _stream.(av.AudioCodecData) - switch typ { - case av.AAC: - metadata["audiocodecid"] = flvio.SOUND_AAC - - default: - err = fmt.Errorf("rtmp: WriteHeader unsupported audio codecType=%v", stream.Type()) - return - } - - metadata["audiodatarate"] = 156 // TODO: make it correct - } - } - - // > onMetaData() - w := self.writeDataMsgStart() - flvio.WriteAMF0Val(w, "onMetaData") - flvio.WriteAMF0Val(w, metadata) - if err = self.writeDataMsgEnd(5, self.playmsgcsid); err != nil { - return - } - - // > Videodata(decoder config) - // > Audiodata(decoder config) - for _, stream := range streams { - switch stream.Type() { - case av.H264: - h264 := stream.(h264parser.CodecData) - videodata := self.makeH264Videodata(flvio.AVC_SEQHDR, h264.AVCDecoderConfRecordBytes()) - w := self.writeVideoDataStart() - videodata.Marshal(w) - if err = self.writeVideoDataEnd(0); err != nil { - return - } - - case av.AAC: - aac := stream.(aacparser.CodecData) - audiodata := self.makeAACAudiodata(aac, flvio.AAC_SEQHDR, aac.MPEG4AudioConfigBytes()) - w := self.writeAudioDataStart() - audiodata.Marshal(w) - if err = self.writeAudioDataEnd(0); err != nil { - return - } - } - } - - self.streams = streams - return -} - -func (self *Conn) makeH264Videodata(pkttype uint8, data []byte) flvio.Videodata { - return flvio.Videodata{ - FrameType: flvio.FRAME_KEY, - CodecID: flvio.VIDEO_H264, - AVCPacketType: pkttype, - Data: data, - } -} - -func (self *Conn) makeAACAudiodata(stream av.AudioCodecData, pkttype uint8, data []byte) flvio.Audiodata { - audiodata := flvio.Audiodata{ - SoundFormat: flvio.SOUND_AAC, - SoundRate: flvio.SOUND_44Khz, - AACPacketType: pkttype, - } - switch stream.SampleFormat().BytesPerSample() { - case 1: - audiodata.SoundSize = flvio.SOUND_8BIT - case 2: - audiodata.SoundSize = flvio.SOUND_16BIT - } - switch stream.ChannelLayout().Count() { - case 1: - audiodata.SoundType = flvio.SOUND_MONO - case 2: - audiodata.SoundType = flvio.SOUND_STEREO - } - return audiodata -} - -func (self *Conn) writeSetChunkSize(size uint32) (err error) { - w := self.writeProtoCtrlMsgStart() - w.WriteU32BE(size) - return self.writeProtoCtrlMsgEnd(msgtypeidSetChunkSize) -} - -func (self *Conn) writeWindowAckSize(size uint32) (err error) { - w := self.writeProtoCtrlMsgStart() - w.WriteU32BE(size) - return self.writeProtoCtrlMsgEnd(msgtypeidWindowAckSize) -} - -func (self *Conn) writeSetPeerBandwidth(acksize uint32, limittype uint8) (err error) { - w := self.writeProtoCtrlMsgStart() - w.WriteU32BE(acksize) - w.WriteU8(limittype) - return self.writeProtoCtrlMsgEnd(msgtypeidSetPeerBandwidth) -} - -func (self *Conn) writeProtoCtrlMsgStart() *pio.Writer { - self.intw.SaveToVecOn() - return self.intw -} - -func (self *Conn) writeProtoCtrlMsgEnd(msgtypeid uint8) (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(2, 0, msgtypeid, 0, msgdatav) -} - -func (self *Conn) writeCommandMsgStart() *pio.Writer { - self.intw.SaveToVecOn() - return self.intw -} - -func (self *Conn) writeCommandMsgEnd(csid uint32, msgcsid uint32) (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(csid, 0, msgtypeidCommandMsgAMF0, msgcsid, msgdatav) -} - -func (self *Conn) writeDataMsgStart() *pio.Writer { - self.intw.SaveToVecOn() - return self.intw -} - -func (self *Conn) writeDataMsgEnd(csid uint32, msgcsid uint32) (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(csid, 0, msgtypeidDataMsgAMF0, msgcsid, msgdatav) -} - -func (self *Conn) writeVideoDataStart() *pio.Writer { - self.intw.SaveToVecOn() - return self.intw -} - -func (self *Conn) writeVideoDataEnd(timestamp uint32) (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(6, timestamp, msgtypeidVideoMsg, self.playmsgcsid, msgdatav) -} - -func (self *Conn) writeAudioDataStart() *pio.Writer { - self.intw.SaveToVecOn() - return self.intw -} - -func (self *Conn) writeAudioDataEnd(timestamp uint32) (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(6, timestamp, msgtypeidAudioMsg, self.playmsgcsid, msgdatav) -} - -func (self *Conn) writeUserControlMsgStart(eventtype uint16) *pio.Writer { - self.intw.SaveToVecOn() - self.intw.WriteU16BE(eventtype) - return self.intw -} - -func (self *Conn) writeUserControlMsgEnd() (err error) { - msgdatav := self.intw.SaveToVecOff() - return self.writeChunks(2, 0, msgtypeidUserControl, 0, msgdatav) -} - -func (self *Conn) writeStreamBegin(msgcsid uint32) (err error) { - w := self.writeUserControlMsgStart(eventtypeStreamBegin) - w.WriteU32BE(msgcsid) - return self.writeUserControlMsgEnd() -} - -func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, msgcsid uint32, msgdatav [][]byte) (err error) { - msgdatalen := pio.VecLen(msgdatav) - - // [Type 0][Type 3][Type 3].... - - // 0 1 2 3 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | timestamp |message length | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message length (cont) |message type id| msg stream id | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message stream id (cont) | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // - // Figure 9 Chunk Message Header – Type 0 - if err = self.bw.WriteU8(byte(csid)&0x3f); err != nil { - return - } - if err = self.bw.WriteU24BE(timestamp); err != nil { - return - } - if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { - return - } - if err = self.bw.WriteU8(msgtypeid); err != nil { - return - } - if err = self.bw.WriteU32BE(msgcsid); err != nil { - return - } - - msgdataoff := 0 - for { - size := msgdatalen - msgdataoff - if size > self.writeMaxChunkSize { - size = self.writeMaxChunkSize - } - - write := pio.VecSlice(msgdatav, msgdataoff, msgdataoff+size) - for _, b := range write { - if _, err = self.bw.Write(b); err != nil { - return - } - } - - msgdataoff += size - if msgdataoff == msgdatalen { - break - } - - // Type 3 - if err = self.bw.WriteU8(byte(csid)&0x3f|3<<6); err != nil { - return - } - } - - fmt.Printf("rtmp: write chunk msgdatalen=%d\n", msgdatalen) - - if err = self.bufw.Flush(); err != nil { - return - } - - return -} - -func (self *Conn) readChunk() (err error) { - var msghdrtype uint8 - var csid uint32 - var header uint8 - if header, err = self.br.ReadU8(); err != nil { - return - } - msghdrtype = header>>6 - - csid = uint32(header)&0x3f - switch csid { - default: // Chunk basic header 1 - case 0: // Chunk basic header 2 - var i uint8 - if i, err = self.br.ReadU8(); err != nil { - return - } - csid = uint32(i)+64 - case 1: // Chunk basic header 3 - var i uint16 - if i, err = self.br.ReadU16BE(); err != nil { - return - } - csid = uint32(i)+64 - } - - var cs *chunkStream - if self.lastcs != nil && self.lastcsid == csid { - cs = self.lastcs - } else { - cs = &chunkStream{} - self.csmap[csid] = cs - } - self.lastcs = cs - self.lastcsid = csid - - var timestamp uint32 - - switch msghdrtype { - case 0: - // 0 1 2 3 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | timestamp |message length | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message length (cont) |message type id| msg stream id | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message stream id (cont) | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // - // Figure 9 Chunk Message Header – Type 0 - if cs.msgdataleft != 0 { - err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) - return - } - var h[]byte - if h, err = self.br.ReadBytes(11); err != nil { - return - } - timestamp = pio.GetU24BE(h[0:3]) - cs.msghdrtype = msghdrtype - cs.msgdatalen = pio.GetU24BE(h[3:6]) - cs.msgtypeid = h[6] - cs.msgsid = pio.GetU32BE(h[7:11]) - if timestamp == 0xffffff { - if timestamp, err = self.br.ReadU32BE(); err != nil { - return - } - cs.hastimeext = true - } else { - cs.hastimeext = false - } - cs.timenow = timestamp - cs.Start() - - case 1: - // 0 1 2 3 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | timestamp delta |message length | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message length (cont) |message type id| - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // - // Figure 10 Chunk Message Header – Type 1 - if cs.msgdataleft != 0 { - err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) - return - } - var h[]byte - if h, err = self.br.ReadBytes(7); err != nil { - return - } - timestamp = pio.GetU24BE(h[0:3]) - cs.msghdrtype = msghdrtype - cs.msgdatalen = pio.GetU24BE(h[3:6]) - cs.msgtypeid = h[6] - if timestamp == 0xffffff { - if timestamp, err = self.br.ReadU32BE(); err != nil { - return - } - cs.hastimeext = true - } else { - cs.hastimeext = false - } - cs.timedelta = timestamp - cs.timenow += timestamp - cs.Start() - - case 2: - // 0 1 2 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | timestamp delta | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // - // Figure 11 Chunk Message Header – Type 2 - if cs.msgdataleft != 0 { - err = fmt.Errorf("rtmp: chunk msgdataleft=%d invalid", cs.msgdataleft) - return - } - var h[]byte - if h, err = self.br.ReadBytes(3); err != nil { - return - } - cs.msghdrtype = msghdrtype - timestamp = pio.GetU24BE(h[0:3]) - if timestamp == 0xffffff { - if timestamp, err = self.br.ReadU32BE(); err != nil { - return - } - cs.hastimeext = true - } else { - cs.hastimeext = false - } - cs.timedelta = timestamp - cs.timenow += timestamp - cs.Start() - - case 3: - if cs.msgdataleft == 0 { - switch cs.msghdrtype { - case 0: - if cs.hastimeext { - if timestamp, err = self.br.ReadU32BE(); err != nil { - return - } - cs.timenow = timestamp - } - case 1, 2: - if cs.hastimeext { - if timestamp, err = self.br.ReadU32BE(); err != nil { - return - } - } else { - timestamp = cs.timedelta - } - cs.timenow += timestamp - } - cs.Start() - } - - default: - err = fmt.Errorf("rtmp: invalid chunk msg header type=%d", msghdrtype) - return - } - - size := int(cs.msgdataleft) - if size > self.readMaxChunkSize { - size = self.readMaxChunkSize - } - off := cs.msgdatalen-cs.msgdataleft - buf := cs.msgdata[off:int(off)+size] - if _, err = io.ReadFull(self.br, buf); err != nil { - return - } - cs.msgdataleft -= uint32(size) - - if true { - fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", - csid, cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) - } - - if cs.msgdataleft == 0 { - if true { - fmt.Println("rtmp: chunk data") - fmt.Print(hex.Dump(cs.msgdata)) - fmt.Printf("%x\n", cs.msgdata) - } - - if err = self.handleMsg(csid, cs.msgtypeid, cs.msgdata); err != nil { - return - } - } - - return -} - -func (self *Conn) handleCommandMsgAMF0(r *pio.Reader) (err error) { - commandname, _ := flvio.ReadAMF0Val(r) - commandtransid, _ := flvio.ReadAMF0Val(r) - commandobj, _ := flvio.ReadAMF0Val(r) - - var ok bool - if self.commandname, ok = commandname.(string); !ok { - err = fmt.Errorf("rtmp: CommandMsgAMF0 command is not string") - return - } - - self.commandobj, _ = commandobj.(flvio.AMFMap) - self.commandtransid, _ = commandtransid.(float64) - self.commandparams = []interface{}{} - for { - if val, rerr := flvio.ReadAMF0Val(r); rerr != nil { - break - } else { - self.commandparams = append(self.commandparams, val) - } - } - - self.gotcommand = true - return -} - -func (self *Conn) handleMsg(msgcsid uint32, msgtypeid uint8, msgdata []byte) (err error) { - self.msgcsid = msgcsid - - switch msgtypeid { - case msgtypeidCommandMsgAMF0: - r := pio.NewReaderBytes(msgdata) - if err = self.handleCommandMsgAMF0(r); err != nil { - return - } - - case msgtypeidCommandMsgAMF3: - r := pio.NewReaderBytes(msgdata) - r.ReadU8() // skip first byte - if err = self.handleCommandMsgAMF0(r); err != nil { - return - } - - case msgtypeidUserControl: - if len(msgdata) >= 2 { - self.eventtype = pio.GetU16BE(msgdata) - } else { - err = fmt.Errorf("rtmp: short packet of UserControl") - return - } - - case msgtypeidSetPeerBandwidth: - case msgtypeidSetChunkSize: - case msgtypeidWindowAckSize: - self.msgdata = msgdata - self.msgtypeid = msgtypeid - - default: - return - } - - self.gotmsg = true - return -} - -var ( - hsClientFullKey = []byte{ - 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', - 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', - '0', '0', '1', - 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, - 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, - 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, - } - hsServerFullKey = []byte{ - 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', - 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', - 'S', 'e', 'r', 'v', 'e', 'r', ' ', - '0', '0', '1', - 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, - 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, - 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, - } - hsClientPartialKey = hsClientFullKey[:30] - hsServerPartialKey = hsServerFullKey[:36] -) - -func hsMakeDigest(key []byte, src []byte, 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 hsFindDigest(p []byte, key []byte, base int) (off int) { - for n := 0; n < 4; n++ { - off += int(p[base + n]) - } - off = (off % 728) + base + 4 - digest := hsMakeDigest(key, p, off) - if bytes.Compare(p[off:off+32], digest) != 0 { - off = -1 - } - return off -} - -func hsParseC1(p []byte) (digest []byte, err error) { - return hsParse1(p, hsClientPartialKey) -} - -func hsParseS1(p []byte) (digest []byte, err error) { - return hsParse1(p, hsServerPartialKey) -} - -func hsParse1(p []byte, key []byte) (digest []byte, err error) { - var off int - if off = hsFindDigest(p, key, 772); off == -1 { - if off = hsFindDigest(p, key, 8); off == -1 { - err = fmt.Errorf("rtmp: handshake: C1 parse failed") - return - } - } - digest = hsMakeDigest(key, p[off:off+32], -1) - return -} - -func hsCreateS1(p []byte) { - hsCreate1(p, hsServerPartialKey) -} - -func hsCreateS2(p []byte, digest []byte) { - rand.Read(p) - digest2 := hsMakeDigest(digest, p, 1536-32) - copy(p[1536-32:], digest2) -} - -func hsCreate1(p []byte, key []byte) { - rand.Read(p) - off := 0 - for n := 8; n < 12; n++ { - off += int(p[n]) - } - off = (off % 728) + 12 - digest := hsMakeDigest(key, p, off) - copy(p[off:], digest) -} - -func (self *Conn) handshake() (err error) { - var version uint8 - - var random [1536*4]byte - var digest []byte - C1 := random[0:1536] - S1 := random[1536:1536*2] - C2 := random[1536*2:1536*3] - S2 := random[1536*3:1536*4] - - // C0 - if version, err = self.br.ReadU8(); err != nil { - return - } - if version != 0x3 { - err = fmt.Errorf("rtmp: handshake C0: version=%d invalid", version) - return - } - // C1 - if _, err = io.ReadFull(self.br, C1); err != nil { - return - } - - // TODO: do the right thing - if false { - if digest, err = hsParseC1(C1); err != nil { - return - } - serverTime := uint32(0) - serverVer := uint32(0x0d0e0a0d) - hsCreateS1(S1) - pio.PutU32BE(S1[0:4], serverTime) - pio.PutU32BE(S1[4:8], serverVer) - hsCreateS2(S2, digest) - } - - // S0 - if err = self.bw.WriteU8(0x3); err != nil { - return - } - // S1 - if _, err = self.bw.Write(S1); err != nil { - return - } - // S2 - if _, err = self.bw.Write(S2); err != nil { - return - } - if err = self.bufw.Flush(); err != nil { - return - } - - // C2 - if _, err = io.ReadFull(self.br, C2); err != nil { - return - } - - return -} - diff --git a/util.go b/util.go deleted file mode 100644 index d22e617..0000000 --- a/util.go +++ /dev/null @@ -1,113 +0,0 @@ - -package rtmp - -import ( - "io" - "log" - "os" - "fmt" - "strings" -) - -type logger int - -func (l logger) Printf(format string, v ...interface{}) { - str := fmt.Sprintf(format, v...) - switch { - case strings.HasPrefix(str, "server") && l >= 1, - strings.HasPrefix(str, "stream") && l >= 1, - strings.HasPrefix(str, "event") && l >= 1, - strings.HasPrefix(str, "data") && l >= 1, - strings.HasPrefix(str, "msg") && l >= 2: - l2.Println(str) - default: - if l >= 1 { - l2.Println(str) - } - } -} - -var ( - l = logger(0) - l2 *log.Logger -) - -func init() { - l2 = log.New(os.Stderr, "", 0) - l2.SetFlags(log.Lmicroseconds) -} - -func LogLevel(i int) { - l = logger(i) -} - -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) -} - From 57541c0a593f634aa09d5301ecb5cfbc4a1aae69 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 20:59:49 +0800 Subject: [PATCH 10/24] add ReadHeader --- server.go | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index a34bfa7..d6410f0 100644 --- a/server.go +++ b/server.go @@ -108,7 +108,9 @@ type Conn struct { Host string Path string Debug bool + streams []av.CodecData + videostreamidx, audiostreamidx int br *pio.Reader bw *pio.Writer @@ -138,6 +140,7 @@ type Conn struct { gotmsg bool msgdata []byte msgtypeid uint8 + datamsgvals []interface{} eventtype uint16 } @@ -363,6 +366,34 @@ func (self *Conn) checkCreateStreamResult() (ok bool, avmsgsid uint32) { return } +func (self *Conn) parseMetaData(metadata flvio.AMFMap) (atype, vtype av.CodecType, err error) { + if v, ok := metadata["videocodecid"].(float64); ok { + codecid := int(v) + switch codecid { + case flvio.VIDEO_H264: + vtype = av.H264 + + default: + err = fmt.Errorf("rtmp: videocodecid=%d unspported", codecid) + return + } + } + + if v, ok := metadata["audiocodecid"].(float64); ok { + codecid := int(v) + switch codecid { + case flvio.SOUND_AAC: + atype = av.AAC + + default: + err = fmt.Errorf("rtmp: audiocodecid=%d unspported", codecid) + return + } + } + + return +} + func (self *Conn) connectPlay() (err error) { var connectpath, playpath string pathsegs := strings.Split(self.Path, "/") @@ -441,8 +472,103 @@ func (self *Conn) connectPlay() (err error) { } } - fmt.Println("rtmp: play", playpath) + // > play('app') + fmt.Printf("rtmp: > play('%s')\n", playpath) + w = self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "play") + flvio.WriteAMF0Val(w, 0) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, playpath) + self.writeCommandMsgEnd(8, self.avmsgsid) + var atype, vtype av.CodecType + for { + if err = self.pollMsg(); err != nil { + return + } + if self.msgtypeid == msgtypeidDataMsgAMF0 { + if len(self.datamsgvals) >= 2 { + name, _ := self.datamsgvals[0].(string) + data, _ := self.datamsgvals[1].(flvio.AMFMap) + if name == "onMetaData" { + if atype, vtype, err = self.parseMetaData(data); err != nil { + return + } + fmt.Printf("rtmp: < onMetaData()\n") + break + } + } + } + } + nrstreams := 0 + if atype != 0 { + nrstreams++ + } + if vtype != 0 { + nrstreams++ + } + + for i := 0; ; i++ { + if err = self.pollMsg(); err != nil { + return + } + + switch { + case self.msgtypeid == msgtypeidVideoMsg && vtype != 0: + tag := &flvio.Videodata{} + r := pio.NewReaderBytes(self.msgdata) + r.LimitOn(int64(len(self.msgdata))) + if err = tag.Unmarshal(r); err != nil { + return + } + switch vtype { + case av.H264: + if tag.AVCPacketType == flvio.AVC_SEQHDR { + var codec h264parser.CodecData + if codec, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil { + err = fmt.Errorf("rtmp: h264 codec data invalid") + return + } + self.videostreamidx = len(self.streams) + self.streams = append(self.streams, codec) + } + } + + case self.msgtypeid == msgtypeidAudioMsg && atype != 0: + tag := &flvio.Audiodata{} + r := pio.NewReaderBytes(self.msgdata) + r.LimitOn(int64(len(self.msgdata))) + if err = tag.Unmarshal(r); err != nil { + return + } + switch atype { + case av.AAC: + if tag.AACPacketType == flvio.AAC_SEQHDR { + var codec aacparser.CodecData + if codec, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil { + err = fmt.Errorf("rtmp: aac codec data invalid") + return + } + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, codec) + } + } + } + + if nrstreams == len(self.streams) { + break + } + + if i > 100 { + err = fmt.Errorf("rtmp: probe failed") + return + } + } + + return +} + +func (self *Conn) ReadPacket() (pkt av.Packet, err error) { return } @@ -982,7 +1108,6 @@ func (self *Conn) handleCommandMsgAMF0(r *pio.Reader) (err error) { } func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err error) { - self.msgdata = msgdata self.msgtypeid = msgtypeid switch msgtypeid { @@ -1002,10 +1127,29 @@ func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err case msgtypeidUserControl: if len(msgdata) >= 2 { self.eventtype = pio.GetU16BE(msgdata) + self.msgdata = msgdata } else { err = fmt.Errorf("rtmp: short packet of UserControl") return } + + case msgtypeidDataMsgAMF0: + r := pio.NewReaderBytes(msgdata) + self.datamsgvals = []interface{}{} + for { + if val, err := flvio.ReadAMF0Val(r); err != nil { + break + } else { + self.datamsgvals = append(self.datamsgvals, val) + } + } + + case msgtypeidVideoMsg, msgtypeidAudioMsg: + self.msgdata = msgdata + + case msgtypeidSetChunkSize: + self.readMaxChunkSize = int(pio.GetU32BE(msgdata)) + return } self.gotmsg = true From 64d8e0b0e42b4b42f8339927942c61bb2e9595a2 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 21:33:25 +0800 Subject: [PATCH 11/24] client can work --- server.go | 127 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 36 deletions(-) diff --git a/server.go b/server.go index d6410f0..a01ebd3 100644 --- a/server.go +++ b/server.go @@ -42,8 +42,6 @@ func DialTimeout(uri string, timeout time.Duration) (conn *Conn, err error) { return } - fmt.Println("rtmp: connected") - conn = NewConn(netconn) conn.Host = host conn.Path = path @@ -138,9 +136,12 @@ type Conn struct { commandparams []interface{} gotmsg bool + timestamp uint32 msgdata []byte msgtypeid uint8 datamsgvals []interface{} + videodata *flvio.Videodata + audiodata *flvio.Audiodata eventtype uint16 } @@ -212,12 +213,14 @@ func (self *Conn) pollCommand() (err error) { func (self *Conn) pollMsg() (err error) { self.gotmsg = false self.gotcommand = false + self.datamsgvals = nil + self.videodata = nil + self.audiodata = nil for { if err = self.readChunk(); err != nil { return } if self.gotmsg { - fmt.Println("rtmp: gotmsg iscommand", self.gotcommand) return } } @@ -317,7 +320,9 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, true) self.writeDataMsgEnd(5, self.avmsgsid) - fmt.Println("rtmp: playing") + if self.Debug { + fmt.Println("rtmp: playing") + } self.Path = fmt.Sprintf("/%s/%s", connectpath, playpath) self.playing = true @@ -405,7 +410,9 @@ func (self *Conn) connectPlay() (err error) { } // > connect("app") - fmt.Printf("rtmp: > connect('%s') host=%s\n", connectpath, self.Host) + if self.Debug { + fmt.Printf("rtmp: > connect('%s') host=%s\n", connectpath, self.Host) + } w := self.writeCommandMsgStart() flvio.WriteAMF0Val(w, "connect") flvio.WriteAMF0Val(w, 1) @@ -434,7 +441,9 @@ func (self *Conn) connectPlay() (err error) { err = fmt.Errorf("rtmp: command connect failed: %s", errmsg) return } - fmt.Printf("rtmp: < _result() of connect\n") + if self.Debug { + fmt.Printf("rtmp: < _result() of connect\n") + } break } } else { @@ -445,15 +454,17 @@ func (self *Conn) connectPlay() (err error) { } // > createStream() - fmt.Printf("rtmp: > createStream()\n") + if self.Debug { + fmt.Printf("rtmp: > createStream()\n") + } w = self.writeCommandMsgStart() flvio.WriteAMF0Val(w, "createStream") flvio.WriteAMF0Val(w, 2) flvio.WriteAMF0Val(w, nil) self.writeCommandMsgEnd(3, 0) - // > SetBufferLength 0,3000ms - self.writeSetBufferLength(0, 3000) + // > SetBufferLength 0,0ms + self.writeSetBufferLength(0, 0) for { if err = self.pollMsg(); err != nil { @@ -473,7 +484,9 @@ func (self *Conn) connectPlay() (err error) { } // > play('app') - fmt.Printf("rtmp: > play('%s')\n", playpath) + if self.Debug { + fmt.Printf("rtmp: > play('%s')\n", playpath) + } w = self.writeCommandMsgStart() flvio.WriteAMF0Val(w, "play") flvio.WriteAMF0Val(w, 0) @@ -494,7 +507,9 @@ func (self *Conn) connectPlay() (err error) { if atype, vtype, err = self.parseMetaData(data); err != nil { return } - fmt.Printf("rtmp: < onMetaData()\n") + if self.Debug { + fmt.Printf("rtmp: < onMetaData()\n") + } break } } @@ -515,12 +530,7 @@ func (self *Conn) connectPlay() (err error) { switch { case self.msgtypeid == msgtypeidVideoMsg && vtype != 0: - tag := &flvio.Videodata{} - r := pio.NewReaderBytes(self.msgdata) - r.LimitOn(int64(len(self.msgdata))) - if err = tag.Unmarshal(r); err != nil { - return - } + tag := self.videodata switch vtype { case av.H264: if tag.AVCPacketType == flvio.AVC_SEQHDR { @@ -535,12 +545,7 @@ func (self *Conn) connectPlay() (err error) { } case self.msgtypeid == msgtypeidAudioMsg && atype != 0: - tag := &flvio.Audiodata{} - r := pio.NewReaderBytes(self.msgdata) - r.LimitOn(int64(len(self.msgdata))) - if err = tag.Unmarshal(r); err != nil { - return - } + tag := self.audiodata switch atype { case av.AAC: if tag.AACPacketType == flvio.AAC_SEQHDR { @@ -569,6 +574,28 @@ func (self *Conn) connectPlay() (err error) { } func (self *Conn) ReadPacket() (pkt av.Packet, err error) { + poll: for { + if err = self.pollMsg(); err != nil { + return + } + switch self.msgtypeid { + case msgtypeidVideoMsg: + tag := self.videodata + pkt.CompositionTime = tsToTime(uint32(tag.CompositionTime)) + pkt.Data = tag.Data + pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY + pkt.Idx = int8(self.videostreamidx) + break poll + + case msgtypeidAudioMsg: + tag := self.audiodata + pkt.Data = tag.Data + pkt.Idx = int8(self.audiostreamidx) + break poll + } + } + + pkt.Time = tsToTime(self.timestamp) return } @@ -589,10 +616,20 @@ func (self *Conn) Streams() (streams []av.CodecData, err error) { return } +func timeToTs(tm time.Duration) uint32 { + return uint32(tm / time.Millisecond) +} + +func tsToTime(ts uint32) time.Duration { + return time.Duration(ts)*time.Millisecond +} + func (self *Conn) WritePacket(pkt av.Packet) (err error) { - ts := uint32(pkt.Time/time.Millisecond) + ts := timeToTs(pkt.Time) stream := self.streams[pkt.Idx] - fmt.Println("rtmp: WritePacket", pkt.Idx, pkt.Time, pkt.CompositionTime, ts) + if self.Debug { + fmt.Println("rtmp: WritePacket", pkt.Idx, pkt.Time, pkt.CompositionTime, ts) + } switch stream.Type() { case av.AAC: @@ -603,7 +640,7 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { case av.H264: videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.IsKeyFrame, pkt.Data) - videodata.CompositionTime = int32(pkt.CompositionTime/time.Millisecond) + videodata.CompositionTime = int32(timeToTs(pkt.CompositionTime)) w := self.writeVideoDataStart() videodata.Marshal(w) self.writeVideoDataEnd(ts) @@ -874,7 +911,9 @@ func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, ms } } - fmt.Printf("rtmp: write chunk msgdatalen=%d msgsid=%d\n", msgdatalen, msgsid) + if self.Debug { + fmt.Printf("rtmp: write chunk msgdatalen=%d msgsid=%d\n", msgdatalen, msgsid) + } if err = self.bufw.Flush(); err != nil { return @@ -1061,19 +1100,18 @@ func (self *Conn) readChunk() (err error) { } cs.msgdataleft -= uint32(size) - if true { + if self.Debug { fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", csid, cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) } if cs.msgdataleft == 0 { - if true { + if self.Debug { fmt.Println("rtmp: chunk data") fmt.Print(hex.Dump(cs.msgdata)) - fmt.Printf("%x\n", cs.msgdata) } - if err = self.handleMsg(cs.msgsid, cs.msgtypeid, cs.msgdata); err != nil { + if err = self.handleMsg(cs.timenow, cs.msgsid, cs.msgtypeid, cs.msgdata); err != nil { return } } @@ -1107,8 +1145,9 @@ func (self *Conn) handleCommandMsgAMF0(r *pio.Reader) (err error) { return } -func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err error) { +func (self *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, msgdata []byte) (err error) { self.msgtypeid = msgtypeid + self.timestamp = timestamp switch msgtypeid { case msgtypeidCommandMsgAMF0: @@ -1135,7 +1174,6 @@ func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err case msgtypeidDataMsgAMF0: r := pio.NewReaderBytes(msgdata) - self.datamsgvals = []interface{}{} for { if val, err := flvio.ReadAMF0Val(r); err != nil { break @@ -1144,8 +1182,23 @@ func (self *Conn) handleMsg(msgsid uint32, msgtypeid uint8, msgdata []byte) (err } } - case msgtypeidVideoMsg, msgtypeidAudioMsg: - self.msgdata = msgdata + case msgtypeidVideoMsg: + tag := &flvio.Videodata{} + r := pio.NewReaderBytes(msgdata) + r.LimitOn(int64(len(msgdata))) + if err = tag.Unmarshal(r); err != nil { + return + } + self.videodata = tag + + case msgtypeidAudioMsg: + tag := &flvio.Audiodata{} + r := pio.NewReaderBytes(msgdata) + r.LimitOn(int64(len(msgdata))) + if err = tag.Unmarshal(r); err != nil { + return + } + self.audiodata = tag case msgtypeidSetChunkSize: self.readMaxChunkSize = int(pio.GetU32BE(msgdata)) @@ -1247,7 +1300,9 @@ func (self *Conn) handshakeClient() (err error) { return } - fmt.Println("rtmp: handshakeClient: server version", S1[4],S1[5],S1[6],S1[7]) + if self.Debug { + fmt.Println("rtmp: handshakeClient: server version", S1[4],S1[5],S1[6],S1[7]) + } if S1[4] >= 3 { } else { From 065ad9b365ffe43a1dfd9cdaea7afc05e68c76aa Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 22:04:37 +0800 Subject: [PATCH 12/24] change to flvio.MakeAACAudiodata --- server.go | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/server.go b/server.go index a01ebd3..24a4635 100644 --- a/server.go +++ b/server.go @@ -740,25 +740,9 @@ func (self *Conn) makeH264Videodata(pkttype uint8, iskeyframe bool, data []byte) } func (self *Conn) makeAACAudiodata(stream av.AudioCodecData, pkttype uint8, data []byte) flvio.Audiodata { - audiodata := flvio.Audiodata{ - SoundFormat: flvio.SOUND_AAC, - SoundRate: flvio.SOUND_44Khz, - AACPacketType: pkttype, - Data: data, - } - switch stream.SampleFormat().BytesPerSample() { - case 1: - audiodata.SoundSize = flvio.SOUND_8BIT - default: - audiodata.SoundSize = flvio.SOUND_16BIT - } - switch stream.ChannelLayout().Count() { - case 1: - audiodata.SoundType = flvio.SOUND_MONO - case 2: - audiodata.SoundType = flvio.SOUND_STEREO - } - return audiodata + tag := flvio.MakeAACAudiodata(stream, data) + tag.AACPacketType = pkttype + return tag } func (self *Conn) writeSetChunkSize(size uint32) (err error) { From 8bda26aad1a43017733c67f5ea79bc5af0a7d6f8 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 26 Jun 2016 22:17:14 +0800 Subject: [PATCH 13/24] fmt --- server.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 24a4635..a351b00 100644 --- a/server.go +++ b/server.go @@ -1203,13 +1203,13 @@ var ( 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, } hsServerFullKey = []byte{ - 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', - 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', - 'S', 'e', 'r', 'v', 'e', 'r', ' ', - '0', '0', '1', - 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, - 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, - 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', + 'S', 'e', 'r', 'v', 'e', 'r', ' ', + '0', '0', '1', + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, } hsClientPartialKey = hsClientFullKey[:30] hsServerPartialKey = hsServerFullKey[:36] From 2432a072da1055ab9ecab74dcb85dd646484a50c Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 27 Jun 2016 01:32:19 +0800 Subject: [PATCH 14/24] add publishing handle --- server.go | 158 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 124 insertions(+), 34 deletions(-) diff --git a/server.go b/server.go index a351b00..8261bab 100644 --- a/server.go +++ b/server.go @@ -49,26 +49,40 @@ func DialTimeout(uri string, timeout time.Duration) (conn *Conn, err error) { } type Server struct { + Debug bool + DebugConn bool Addr string HandlePublish func(*Conn) HandlePlay func(*Conn) } func (self *Server) handleConn(conn *Conn) (err error) { - if err = conn.handshake(); err != nil { + if err = conn.handshakeServer(); err != nil { return } - if err = conn.determineType(); err != nil { - fmt.Println("rtmp: conn closed:", err) return } if conn.playing { if self.HandlePlay != nil { self.HandlePlay(conn) - conn.Close() } + } else if conn.publishing { + if self.HandlePublish != nil { + self.HandlePublish(conn) + } + + for { + conn.pollMsg() + if conn.msgtypeid == msgtypeidAudioMsg || conn.msgtypeid == msgtypeidVideoMsg { + break + } + } + } + + if err = conn.Close(); err != nil { + return } return @@ -96,7 +110,12 @@ func (self *Server) ListenAndServe() (err error) { return } + if self.Debug { + fmt.Println("rtmp: server: accepted") + } + conn := NewConn(netconn) + conn.Debug = self.DebugConn conn.isserver = true go self.handleConn(conn) } @@ -120,8 +139,6 @@ type Conn struct { writeMaxChunkSize int readMaxChunkSize int - lastcsid uint32 - lastcs *chunkStream csmap map[uint32]*chunkStream isserver bool @@ -152,8 +169,8 @@ func NewConn(netconn net.Conn) *Conn { conn.csmap = make(map[uint32]*chunkStream) conn.readMaxChunkSize = 128 conn.writeMaxChunkSize = 128 - conn.bufr = bufio.NewReaderSize(netconn, 4096) - conn.bufw = bufio.NewWriterSize(netconn, 4096) + conn.bufr = bufio.NewReaderSize(netconn, 2048) + conn.bufw = bufio.NewWriterSize(netconn, 2048) conn.br = pio.NewReader(conn.bufr) conn.bw = pio.NewWriter(conn.bufw) conn.intw = pio.NewWriter(nil) @@ -227,7 +244,7 @@ func (self *Conn) pollMsg() (err error) { } func (self *Conn) determineType() (err error) { - var connectpath, playpath string + var connectpath string // < connect("app") if err = self.pollCommand(); err != nil { @@ -290,13 +307,46 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, self.avmsgsid) // streamid=1 self.writeCommandMsgEnd(3, 0) - // < play("path") - case "play": + // < publish("path") + case "publish": + if self.Debug { + fmt.Println("rtmp: < publish") + } + if len(self.commandparams) < 1 { - err = fmt.Errorf("rtmp: play params invalid") + err = fmt.Errorf("rtmp: publish params invalid") return } - playpath, _ = self.commandparams[0].(string) + publishpath, _ := self.commandparams[0].(string) + + // > onStatus() + w := self.writeCommandMsgStart() + flvio.WriteAMF0Val(w, "onStatus") + flvio.WriteAMF0Val(w, self.commandtransid) + flvio.WriteAMF0Val(w, nil) + flvio.WriteAMF0Val(w, flvio.AMFMap{ + "level": "status", + "code": "NetStream.Publish.Start", + "description": "Start publishing", + }) + self.writeCommandMsgEnd(5, self.avmsgsid) + + self.Path = fmt.Sprintf("/%s/%s", connectpath, publishpath) + self.publishing = true + self.reading = true + return + + // < play("path") + case "play": + if self.Debug { + fmt.Println("rtmp: < play") + } + + if len(self.commandparams) < 1 { + err = fmt.Errorf("rtmp: command play params invalid") + return + } + playpath, _ := self.commandparams[0].(string) // > streamBegin(streamid) self.writeStreamBegin(self.avmsgsid) @@ -320,10 +370,6 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, true) self.writeDataMsgEnd(5, self.avmsgsid) - if self.Debug { - fmt.Println("rtmp: playing") - } - self.Path = fmt.Sprintf("/%s/%s", connectpath, playpath) self.playing = true self.writing = true @@ -592,6 +638,12 @@ func (self *Conn) ReadPacket() (pkt av.Packet, err error) { pkt.Data = tag.Data pkt.Idx = int8(self.audiostreamidx) break poll + + case msgtypeidUserControl: + + default: + err = fmt.Errorf("debug %d %v", self.msgtypeid, self.msgdata) + return } } @@ -601,7 +653,7 @@ func (self *Conn) ReadPacket() (pkt av.Packet, err error) { func (self *Conn) ReadHeader() (err error) { if !self.reading && !self.writing { - if err = self.handshake(); err != nil { + if err = self.handshakeClient(); err != nil { return } if err = self.connectPlay(); err != nil { @@ -897,6 +949,11 @@ func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, ms if self.Debug { fmt.Printf("rtmp: write chunk msgdatalen=%d msgsid=%d\n", msgdatalen, msgsid) + b := []byte{} + for _, a := range msgdatav { + b = append(b, a...) + } + fmt.Print(hex.Dump(b)) } if err = self.bufw.Flush(); err != nil { @@ -932,15 +989,11 @@ func (self *Conn) readChunk() (err error) { csid = uint32(i)+64 } - var cs *chunkStream - if self.lastcs != nil && self.lastcsid == csid { - cs = self.lastcs - } else { + cs := self.csmap[csid] + if cs == nil { cs = &chunkStream{} self.csmap[csid] = cs } - self.lastcs = cs - self.lastcsid = csid var timestamp uint32 @@ -1085,8 +1138,8 @@ func (self *Conn) readChunk() (err error) { cs.msgdataleft -= uint32(size) if self.Debug { - fmt.Printf("rtmp: chunk csid=%d msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", - csid, cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) + fmt.Printf("rtmp: chunk msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n", + cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft) } if cs.msgdataleft == 0 { @@ -1245,14 +1298,6 @@ func hsCreateC1(p []byte) { copy(p[gap:], digest) } -func (self *Conn) handshake() (err error) { - if self.isserver { - return self.handshakeServer() - } else { - return self.handshakeClient() - } -} - func (self *Conn) handshakeClient() (err error) { var random [(1+1536*2)*2]byte @@ -1289,6 +1334,9 @@ func (self *Conn) handshakeClient() (err error) { } if S1[4] >= 3 { + // TODO + err = fmt.Errorf("rtmp: newstyle handshake unspported") + return } else { C2 = S1 } @@ -1305,6 +1353,48 @@ func (self *Conn) handshakeClient() (err error) { } func (self *Conn) handshakeServer() (err error) { + var random [(1+1536*2)*2]byte + + C0C1C2 := random[:1536*2+1] + C0 := C0C1C2[:1] + C1 := C0C1C2[1:1536+1] + C0C1 := C0C1C2[:1536+1] + C2 := C0C1C2[1536+1:] + + S0S1S2 := random[1536*2+1:] + S0 := S0S1S2[:1] + S1 := S0S1S2[1:1536+1] + //S0S1 := S0S1S2[:1536+1] + S2 := S0S1S2[1536+1:] + + // < C0C1 + if _, err = io.ReadFull(self.br, C0C1); err != nil { + return + } + if C0[0] != 3 { + err = fmt.Errorf("rtmp: handshake version=%d invalid", C0[0]) + return + } + + S0[0] = 3 + copy(S1[0:4], C1[0:4]) + rand.Read(S1[8:]) + copy(S2[0:4], C1[0:4]) + copy(S2[8:], C1[8:]) + + // > S0S1S2 + if _, err = self.bw.Write(S0S1S2); err != nil { + return + } + if err = self.bufw.Flush(); err != nil { + return + } + + // < C2 + if _, err = io.ReadFull(self.br, C2); err != nil { + return + } + return } From 6e4e8031d37f05311af6ba0ad1c987e2d63b5567 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 28 Jun 2016 15:28:51 +0800 Subject: [PATCH 15/24] multiple changes, now mpv can play --- server.go | 428 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 264 insertions(+), 164 deletions(-) diff --git a/server.go b/server.go index 8261bab..3abca1f 100644 --- a/server.go +++ b/server.go @@ -14,6 +14,7 @@ import ( "github.com/nareix/pio" "github.com/nareix/flv/flvio" "github.com/nareix/av" + "github.com/nareix/codec" "github.com/nareix/codec/h264parser" "github.com/nareix/codec/aacparser" "crypto/hmac" @@ -21,6 +22,8 @@ import ( "crypto/rand" ) +var MaxProbePacketCount = 6 + func ParseURL(uri string) (host string, path string) { var u *url.URL if u, _ = url.Parse(uri); u == nil { @@ -34,6 +37,20 @@ func ParseURL(uri string) (host string, path string) { return } +func Open(uri string) (conn *Conn, err error) { + if conn, err = Dial(uri); err != nil { + return + } + if err = conn.ReadHeader(); err != nil { + return + } + return +} + +func Dial(uri string) (conn *Conn, err error) { + return DialTimeout(uri, 0) +} + func DialTimeout(uri string, timeout time.Duration) (conn *Conn, err error) { host, path := ParseURL(uri) dailer := net.Dialer{Timeout: timeout} @@ -72,13 +89,6 @@ func (self *Server) handleConn(conn *Conn) (err error) { if self.HandlePublish != nil { self.HandlePublish(conn) } - - for { - conn.pollMsg() - if conn.msgtypeid == msgtypeidAudioMsg || conn.msgtypeid == msgtypeidVideoMsg { - break - } - } } if err = conn.Close(); err != nil { @@ -117,7 +127,12 @@ func (self *Server) ListenAndServe() (err error) { conn := NewConn(netconn) conn.Debug = self.DebugConn conn.isserver = true - go self.handleConn(conn) + go func() { + err = self.handleConn(conn) + if self.Debug { + fmt.Println("rtmp: server: client closed err:", err) + } + }() } } @@ -129,6 +144,8 @@ type Conn struct { streams []av.CodecData videostreamidx, audiostreamidx int + probepkts []flvio.Tag + br *pio.Reader bw *pio.Writer bufr *bufio.Reader @@ -138,12 +155,13 @@ type Conn struct { writeMaxChunkSize int readMaxChunkSize int - - csmap map[uint32]*chunkStream + readcsmap map[uint32]*chunkStream + writecsmap map[uint32]*chunkStream isserver bool publishing, playing bool reading, writing bool + avmsgsid uint32 gotcommand bool @@ -166,7 +184,8 @@ type Conn struct { func NewConn(netconn net.Conn) *Conn { conn := &Conn{} conn.netconn = netconn - conn.csmap = make(map[uint32]*chunkStream) + conn.readcsmap = make(map[uint32]*chunkStream) + conn.writecsmap = make(map[uint32]*chunkStream) conn.readMaxChunkSize = 128 conn.writeMaxChunkSize = 128 conn.bufr = bufio.NewReaderSize(netconn, 2048) @@ -243,6 +262,28 @@ func (self *Conn) pollMsg() (err error) { } } +func splitPath(s string) (app, play string) { + pathsegs := strings.Split(s, "/") + if len(pathsegs) > 1 { + app = pathsegs[1] + } + if len(pathsegs) > 2 { + play = pathsegs[2] + } + return +} + +func formatPath(app, play string) string { + ps := strings.Split(app+"/"+play, "/") + out := []string{""} + for _, s := range ps { + if len(s) > 0 { + out = append(out, s) + } + } + return strings.Join(out, "/") +} + func (self *Conn) determineType() (err error) { var connectpath string @@ -331,7 +372,7 @@ func (self *Conn) determineType() (err error) { }) self.writeCommandMsgEnd(5, self.avmsgsid) - self.Path = fmt.Sprintf("/%s/%s", connectpath, publishpath) + self.Path = formatPath(connectpath, publishpath) self.publishing = true self.reading = true return @@ -370,7 +411,7 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, true) self.writeDataMsgEnd(5, self.avmsgsid) - self.Path = fmt.Sprintf("/%s/%s", connectpath, playpath) + self.Path = formatPath(connectpath, playpath) self.playing = true self.writing = true return @@ -417,43 +458,97 @@ func (self *Conn) checkCreateStreamResult() (ok bool, avmsgsid uint32) { return } -func (self *Conn) parseMetaData(metadata flvio.AMFMap) (atype, vtype av.CodecType, err error) { - if v, ok := metadata["videocodecid"].(float64); ok { - codecid := int(v) - switch codecid { - case flvio.VIDEO_H264: - vtype = av.H264 - - default: - err = fmt.Errorf("rtmp: videocodecid=%d unspported", codecid) +func (self *Conn) probe() (err error) { + for i := 0; i < MaxProbePacketCount; { + if err = self.pollMsg(); err != nil { return } + + switch self.msgtypeid { + case msgtypeidVideoMsg: + i++ + tag := self.videodata + switch tag.CodecID { + case flvio.VIDEO_H264: + if tag.AVCPacketType == flvio.AVC_SEQHDR { + var h264 h264parser.CodecData + if h264, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil { + err = fmt.Errorf("rtmp: h264 codec data invalid") + return + } + self.videostreamidx = len(self.streams) + self.streams = append(self.streams, h264) + } else { + self.probepkts = append(self.probepkts, tag) + } + + default: + err = fmt.Errorf("rtmp: video CodecID=%d not supported", tag.CodecID) + return + } + + case msgtypeidAudioMsg: + i++ + tag := self.audiodata + switch tag.SoundFormat { + case flvio.SOUND_AAC: + if tag.AACPacketType == flvio.AAC_SEQHDR { + var aac aacparser.CodecData + if aac, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil { + err = fmt.Errorf("rtmp: aac codec data invalid") + return + } + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, aac) + } else { + self.probepkts = append(self.probepkts, tag) + } + + case flvio.SOUND_NELLYMOSER, flvio.SOUND_NELLYMOSER_16KHZ_MONO, flvio.SOUND_NELLYMOSER_8KHZ_MONO: + stream := codec.NewNellyMoserCodecData() + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, stream) + self.probepkts = append(self.probepkts, tag) + + case flvio.SOUND_ALAW: + stream := codec.NewPCMAlawCodecData() + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, stream) + self.probepkts = append(self.probepkts, tag) + + case flvio.SOUND_MULAW: + stream := codec.NewPCMMulawCodecData() + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, stream) + self.probepkts = append(self.probepkts, tag) + + case flvio.SOUND_SPEEX: + stream := codec.NewSpeexCodecData() + self.audiostreamidx = len(self.streams) + self.streams = append(self.streams, stream) + self.probepkts = append(self.probepkts, tag) + + default: + err = fmt.Errorf("rtmp: audio SoundFormat=%d not supported", tag.SoundFormat) + return + } + } + + if len(self.streams) == 2 { + break + } } - if v, ok := metadata["audiocodecid"].(float64); ok { - codecid := int(v) - switch codecid { - case flvio.SOUND_AAC: - atype = av.AAC - - default: - err = fmt.Errorf("rtmp: audiocodecid=%d unspported", codecid) - return - } + if len(self.streams) == 0 { + err = fmt.Errorf("rtmp: probe failed") + return } return } func (self *Conn) connectPlay() (err error) { - var connectpath, playpath string - pathsegs := strings.Split(self.Path, "/") - if len(pathsegs) > 1 { - connectpath = pathsegs[1] - } - if len(pathsegs) > 2 { - playpath = pathsegs[2] - } + connectpath, playpath := splitPath(self.Path) // > connect("app") if self.Debug { @@ -509,8 +604,8 @@ func (self *Conn) connectPlay() (err error) { flvio.WriteAMF0Val(w, nil) self.writeCommandMsgEnd(3, 0) - // > SetBufferLength 0,0ms - self.writeSetBufferLength(0, 0) + // > SetBufferLength 0,100ms + self.writeSetBufferLength(0, 100) for { if err = self.pollMsg(); err != nil { @@ -540,110 +635,42 @@ func (self *Conn) connectPlay() (err error) { flvio.WriteAMF0Val(w, playpath) self.writeCommandMsgEnd(8, self.avmsgsid) - var atype, vtype av.CodecType - for { - if err = self.pollMsg(); err != nil { - return - } - if self.msgtypeid == msgtypeidDataMsgAMF0 { - if len(self.datamsgvals) >= 2 { - name, _ := self.datamsgvals[0].(string) - data, _ := self.datamsgvals[1].(flvio.AMFMap) - if name == "onMetaData" { - if atype, vtype, err = self.parseMetaData(data); err != nil { - return - } - if self.Debug { - fmt.Printf("rtmp: < onMetaData()\n") - } - break - } - } - } - } - nrstreams := 0 - if atype != 0 { - nrstreams++ - } - if vtype != 0 { - nrstreams++ - } - - for i := 0; ; i++ { - if err = self.pollMsg(); err != nil { - return - } - - switch { - case self.msgtypeid == msgtypeidVideoMsg && vtype != 0: - tag := self.videodata - switch vtype { - case av.H264: - if tag.AVCPacketType == flvio.AVC_SEQHDR { - var codec h264parser.CodecData - if codec, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil { - err = fmt.Errorf("rtmp: h264 codec data invalid") - return - } - self.videostreamidx = len(self.streams) - self.streams = append(self.streams, codec) - } - } - - case self.msgtypeid == msgtypeidAudioMsg && atype != 0: - tag := self.audiodata - switch atype { - case av.AAC: - if tag.AACPacketType == flvio.AAC_SEQHDR { - var codec aacparser.CodecData - if codec, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil { - err = fmt.Errorf("rtmp: aac codec data invalid") - return - } - self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, codec) - } - } - } - - if nrstreams == len(self.streams) { - break - } - - if i > 100 { - err = fmt.Errorf("rtmp: probe failed") - return - } - } - + self.reading = true + self.playing = true return } func (self *Conn) ReadPacket() (pkt av.Packet, err error) { poll: for { - if err = self.pollMsg(); err != nil { - return + var _tag flvio.Tag + + if len(self.probepkts) > 0 { + _tag = self.probepkts[0] + self.probepkts = self.probepkts[1:] + } else { + if err = self.pollMsg(); err != nil { + return + } + switch self.msgtypeid { + case msgtypeidVideoMsg: + _tag = self.videodata + case msgtypeidAudioMsg: + _tag = self.audiodata + } } - switch self.msgtypeid { - case msgtypeidVideoMsg: - tag := self.videodata + + switch tag := _tag.(type) { + case *flvio.Videodata: pkt.CompositionTime = tsToTime(uint32(tag.CompositionTime)) pkt.Data = tag.Data pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY pkt.Idx = int8(self.videostreamidx) break poll - case msgtypeidAudioMsg: - tag := self.audiodata + case *flvio.Audiodata: pkt.Data = tag.Data pkt.Idx = int8(self.audiostreamidx) break poll - - case msgtypeidUserControl: - - default: - err = fmt.Errorf("debug %d %v", self.msgtypeid, self.msgdata) - return } } @@ -659,6 +686,13 @@ func (self *Conn) ReadHeader() (err error) { if err = self.connectPlay(); err != nil { return } + if err = self.probe(); err != nil { + return + } + } else if self.publishing && self.reading { + if err = self.probe(); err != nil { + return + } } return } @@ -702,9 +736,8 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { } func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { - metadata := flvio.AMFMap{} + metadata := flvio.AMFECMAArray{} - metadata["server"] = "joy4 streaming server (github.com/nareix/joy4)" metadata["duration"] = 0 for _, _stream := range streams { @@ -724,7 +757,7 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { metadata["width"] = stream.Width() metadata["height"] = stream.Height() metadata["framerate"] = 24 // TODO: make it correct - metadata["videodatarate"] = 0 + metadata["videodatarate"] = 1538 case typ.IsAudio(): stream := _stream.(av.AudioCodecData) @@ -737,7 +770,7 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { return } - metadata["audiodatarate"] = 0 + metadata["audiodatarate"] = 156 } } @@ -884,7 +917,7 @@ func (self *Conn) writeStreamBegin(msgsid uint32) (err error) { } func (self *Conn) writeSetBufferLength(msgsid uint32, timestamp uint32) (err error) { - w := self.writeUserControlMsgStart(eventtypeStreamBegin) + w := self.writeUserControlMsgStart(eventtypeSetBufferLength) w.WriteU32BE(msgsid) w.WriteU32BE(timestamp) return self.writeUserControlMsgEnd() @@ -892,34 +925,95 @@ func (self *Conn) writeSetBufferLength(msgsid uint32, timestamp uint32) (err err func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, msgsid uint32, msgdatav [][]byte) (err error) { msgdatalen := pio.VecLen(msgdatav) + msghdrtype := 0 + var tsdelta uint32 - // [Type 0][Type 3][Type 3].... + cs := self.writecsmap[csid] + if cs == nil { + cs = &chunkStream{} + self.writecsmap[csid] = cs + } else { + if msgsid == cs.msgsid { + if uint32(msgdatalen) == cs.msgdatalen && msgtypeid == cs.msgtypeid { + if timestamp == cs.timenow { + msghdrtype = 3 + } else { + msghdrtype = 2 + } + } else { + msghdrtype = 1 + } + } + tsdelta = timestamp - cs.timenow + } + cs.timenow = timestamp + cs.msgdatalen = uint32(msgdatalen) + cs.msgtypeid = msgtypeid + cs.msgsid = msgsid - // 0 1 2 3 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | timestamp |message length | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message length (cont) |message type id| msg stream id | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | message stream id (cont) | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // - // Figure 9 Chunk Message Header – Type 0 - if err = self.bw.WriteU8(byte(csid)&0x3f); err != nil { + if err = self.bw.WriteU8(byte(csid)&0x3f|byte(msghdrtype)<<6); err != nil { return } - if err = self.bw.WriteU24BE(timestamp); err != nil { - return - } - if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { - return - } - if err = self.bw.WriteU8(msgtypeid); err != nil { - return - } - if err = self.bw.WriteU32LE(msgsid); err != nil { - return + + switch msghdrtype { + case 0: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| msg stream id | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message stream id (cont) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 9 Chunk Message Header – Type 0 + if err = self.bw.WriteU24BE(timestamp); err != nil { + return + } + if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { + return + } + if err = self.bw.WriteU8(msgtypeid); err != nil { + return + } + if err = self.bw.WriteU32LE(msgsid); err != nil { + return + } + + case 1: + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta |message length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | message length (cont) |message type id| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 10 Chunk Message Header – Type 1 + if err = self.bw.WriteU24BE(tsdelta); err != nil { + return + } + if err = self.bw.WriteU24BE(uint32(msgdatalen)); err != nil { + return + } + if err = self.bw.WriteU8(msgtypeid); err != nil { + return + } + + case 2: + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp delta | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // Figure 11 Chunk Message Header – Type 2 + if err = self.bw.WriteU24BE(tsdelta); err != nil { + return + } + + case 3: } msgdataoff := 0 @@ -989,10 +1083,10 @@ func (self *Conn) readChunk() (err error) { csid = uint32(i)+64 } - cs := self.csmap[csid] + cs := self.readcsmap[csid] if cs == nil { cs = &chunkStream{} - self.csmap[csid] = cs + self.readcsmap[csid] = cs } var timestamp uint32 @@ -1335,8 +1429,9 @@ func (self *Conn) handshakeClient() (err error) { if S1[4] >= 3 { // TODO - err = fmt.Errorf("rtmp: newstyle handshake unspported") - return + C2 = S1 + //err = fmt.Errorf("rtmp: newstyle handshake unspported") + //return } else { C2 = S1 } @@ -1376,6 +1471,11 @@ func (self *Conn) handshakeServer() (err error) { return } + if false { + epoch := pio.GetU32BE(C1[0:4]) + fmt.Println("rtmp: handshake client epoch", epoch) + } + S0[0] = 3 copy(S1[0:4], C1[0:4]) rand.Read(S1[8:]) From 7d7a93ce1d4d79e60b409e7b8c5f97efc8e2655e Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 28 Jun 2016 15:36:10 +0800 Subject: [PATCH 16/24] add error check --- server.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 3abca1f..f0cef72 100644 --- a/server.go +++ b/server.go @@ -567,7 +567,9 @@ func (self *Conn) connectPlay() (err error) { "videoCodecs": 252, "videoFunction": 1, }) - self.writeCommandMsgEnd(3, 0) + if err = self.writeCommandMsgEnd(3, 0); err != nil { + return + } for { if err = self.pollMsg(); err != nil { @@ -589,7 +591,9 @@ func (self *Conn) connectPlay() (err error) { } } else { if self.msgtypeid == msgtypeidWindowAckSize { - self.writeWindowAckSize(2500000) + if err = self.writeWindowAckSize(2500000); err != nil { + return + } } } } @@ -602,10 +606,14 @@ func (self *Conn) connectPlay() (err error) { flvio.WriteAMF0Val(w, "createStream") flvio.WriteAMF0Val(w, 2) flvio.WriteAMF0Val(w, nil) - self.writeCommandMsgEnd(3, 0) + if err = self.writeCommandMsgEnd(3, 0); err != nil { + return + } // > SetBufferLength 0,100ms - self.writeSetBufferLength(0, 100) + if err = self.writeSetBufferLength(0, 100); err != nil { + return + } for { if err = self.pollMsg(); err != nil { @@ -633,7 +641,9 @@ func (self *Conn) connectPlay() (err error) { flvio.WriteAMF0Val(w, 0) flvio.WriteAMF0Val(w, nil) flvio.WriteAMF0Val(w, playpath) - self.writeCommandMsgEnd(8, self.avmsgsid) + if err = self.writeCommandMsgEnd(8, self.avmsgsid); err != nil { + return + } self.reading = true self.playing = true @@ -722,14 +732,18 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { audiodata := self.makeAACAudiodata(stream.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) w := self.writeAudioDataStart() audiodata.Marshal(w) - self.writeAudioDataEnd(ts) + if err = self.writeAudioDataEnd(ts); err != nil { + return + } case av.H264: videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.IsKeyFrame, pkt.Data) videodata.CompositionTime = int32(timeToTs(pkt.CompositionTime)) w := self.writeVideoDataStart() videodata.Marshal(w) - self.writeVideoDataEnd(ts) + if err = self.writeVideoDataEnd(ts); err != nil { + return + } } return @@ -771,6 +785,8 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { } metadata["audiodatarate"] = 156 + metadata["audiosamplerate"] = 3 + metadata["audiosamplesize"] = 1 } } From be7b15bd461788fb40730afaef165e1c6c8bbccb Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 29 Jun 2016 06:53:18 +0800 Subject: [PATCH 17/24] chang metadata, add stream --- server.go | 83 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/server.go b/server.go index f0cef72..f1d26c2 100644 --- a/server.go +++ b/server.go @@ -136,12 +136,22 @@ func (self *Server) ListenAndServe() (err error) { } } +type stream struct { + codec av.CodecData +} + +func newStream(codec av.CodecData) *stream { + return &stream{ + codec: codec, + } +} + type Conn struct { Host string Path string Debug bool - streams []av.CodecData + streams []*stream videostreamidx, audiostreamidx int probepkts []flvio.Tag @@ -229,6 +239,7 @@ const ( const ( eventtypeStreamBegin = 0 eventtypeSetBufferLength = 3 + eventtypeStreamIsRecorded = 4 ) func (self *Conn) Close() (err error) { @@ -308,7 +319,7 @@ func (self *Conn) determineType() (err error) { if err = self.writeSetPeerBandwidth(5000000, 2); err != nil { return } - self.writeMaxChunkSize = 4096 + self.writeMaxChunkSize = 4000 // > SetChunkSize if err = self.writeSetChunkSize(uint32(self.writeMaxChunkSize)); err != nil { return @@ -328,7 +339,9 @@ func (self *Conn) determineType() (err error) { "description": "Connection Success.", "objectEncoding": 3, }) - self.writeCommandMsgEnd(3, 0) + if err = self.writeCommandMsgEnd(3, 0); err != nil { + return + } for { if err = self.pollMsg(); err != nil { @@ -346,7 +359,9 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, self.commandtransid) flvio.WriteAMF0Val(w, nil) flvio.WriteAMF0Val(w, self.avmsgsid) // streamid=1 - self.writeCommandMsgEnd(3, 0) + if err = self.writeCommandMsgEnd(3, 0); err != nil { + return + } // < publish("path") case "publish": @@ -370,7 +385,9 @@ func (self *Conn) determineType() (err error) { "code": "NetStream.Publish.Start", "description": "Start publishing", }) - self.writeCommandMsgEnd(5, self.avmsgsid) + if err = self.writeCommandMsgEnd(5, self.avmsgsid); err != nil { + return + } self.Path = formatPath(connectpath, publishpath) self.publishing = true @@ -390,7 +407,9 @@ func (self *Conn) determineType() (err error) { playpath, _ := self.commandparams[0].(string) // > streamBegin(streamid) - self.writeStreamBegin(self.avmsgsid) + if err = self.writeStreamBegin(self.avmsgsid); err != nil { + return + } // > onStatus() w := self.writeCommandMsgStart() @@ -402,14 +421,25 @@ func (self *Conn) determineType() (err error) { "code": "NetStream.Play.Start", "description": "Start live", }) - self.writeCommandMsgEnd(5, self.avmsgsid) + if err = self.writeCommandMsgEnd(5, self.avmsgsid); err != nil { + return + } + + // > Stream Is Recored + w = self.writeUserControlMsgStart(eventtypeStreamIsRecorded) + w.WriteU32BE(self.avmsgsid) + if err = self.writeUserControlMsgEnd(); err != nil { + return + } // > |RtmpSampleAccess() w = self.writeDataMsgStart() flvio.WriteAMF0Val(w, "|RtmpSampleAccess") flvio.WriteAMF0Val(w, true) flvio.WriteAMF0Val(w, true) - self.writeDataMsgEnd(5, self.avmsgsid) + if err = self.writeDataMsgEnd(5, self.avmsgsid); err != nil { + return + } self.Path = formatPath(connectpath, playpath) self.playing = true @@ -477,7 +507,7 @@ func (self *Conn) probe() (err error) { return } self.videostreamidx = len(self.streams) - self.streams = append(self.streams, h264) + self.streams = append(self.streams, newStream(h264)) } else { self.probepkts = append(self.probepkts, tag) } @@ -499,7 +529,7 @@ func (self *Conn) probe() (err error) { return } self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, aac) + self.streams = append(self.streams, newStream(aac)) } else { self.probepkts = append(self.probepkts, tag) } @@ -507,25 +537,25 @@ func (self *Conn) probe() (err error) { case flvio.SOUND_NELLYMOSER, flvio.SOUND_NELLYMOSER_16KHZ_MONO, flvio.SOUND_NELLYMOSER_8KHZ_MONO: stream := codec.NewNellyMoserCodecData() self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, stream) + self.streams = append(self.streams, newStream(stream)) self.probepkts = append(self.probepkts, tag) case flvio.SOUND_ALAW: stream := codec.NewPCMAlawCodecData() self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, stream) + self.streams = append(self.streams, newStream(stream)) self.probepkts = append(self.probepkts, tag) case flvio.SOUND_MULAW: stream := codec.NewPCMMulawCodecData() self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, stream) + self.streams = append(self.streams, newStream(stream)) self.probepkts = append(self.probepkts, tag) case flvio.SOUND_SPEEX: stream := codec.NewSpeexCodecData() self.audiostreamidx = len(self.streams) - self.streams = append(self.streams, stream) + self.streams = append(self.streams, newStream(stream)) self.probepkts = append(self.probepkts, tag) default: @@ -708,7 +738,9 @@ func (self *Conn) ReadHeader() (err error) { } func (self *Conn) Streams() (streams []av.CodecData, err error) { - streams = self.streams + for _, stream := range self.streams { + streams = append(streams, stream.codec) + } return } @@ -721,15 +753,17 @@ func tsToTime(ts uint32) time.Duration { } func (self *Conn) WritePacket(pkt av.Packet) (err error) { - ts := timeToTs(pkt.Time) stream := self.streams[pkt.Idx] + ts := timeToTs(pkt.Time) + if self.Debug { fmt.Println("rtmp: WritePacket", pkt.Idx, pkt.Time, pkt.CompositionTime, ts) } - switch stream.Type() { + codec := stream.codec + switch codec.Type() { case av.AAC: - audiodata := self.makeAACAudiodata(stream.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) + audiodata := self.makeAACAudiodata(codec.(av.AudioCodecData), flvio.AAC_RAW, pkt.Data) w := self.writeAudioDataStart() audiodata.Marshal(w) if err = self.writeAudioDataEnd(ts); err != nil { @@ -752,8 +786,6 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { metadata := flvio.AMFECMAArray{} - metadata["duration"] = 0 - for _, _stream := range streams { typ := _stream.Type() switch { @@ -770,8 +802,8 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { metadata["width"] = stream.Width() metadata["height"] = stream.Height() - metadata["framerate"] = 24 // TODO: make it correct - metadata["videodatarate"] = 1538 + metadata["displayWidth"] = stream.Width() + metadata["displayHeight"] = stream.Height() case typ.IsAudio(): stream := _stream.(av.AudioCodecData) @@ -784,9 +816,7 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { return } - metadata["audiodatarate"] = 156 - metadata["audiosamplerate"] = 3 - metadata["audiosamplesize"] = 1 + metadata["audiosamplerate"] = stream.SampleRate() } } @@ -810,6 +840,7 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { if err = self.writeVideoDataEnd(0); err != nil { return } + self.streams = append(self.streams, newStream(stream)) case av.AAC: aac := stream.(aacparser.CodecData) @@ -819,10 +850,10 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { if err = self.writeAudioDataEnd(0); err != nil { return } + self.streams = append(self.streams, newStream(stream)) } } - self.streams = streams return } From 57fa27e32d8aa48caafc5e2d512fc5648ea7994a Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 12:43:13 +0800 Subject: [PATCH 18/24] fix server handshake --- server.go | 106 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/server.go b/server.go index f1d26c2..43a973d 100644 --- a/server.go +++ b/server.go @@ -3,7 +3,7 @@ package rtmp import ( "strings" - _"bytes" + "bytes" "net" "net/url" "bufio" @@ -114,6 +114,10 @@ func (self *Server) ListenAndServe() (err error) { return } + if self.Debug { + fmt.Println("rtmp: server: listening on", addr) + } + var netconn net.Conn for { if netconn, err = listener.Accept(); err != nil { @@ -336,7 +340,7 @@ func (self *Conn) determineType() (err error) { flvio.WriteAMF0Val(w, flvio.AMFMap{ "level": "status", "code": "NetConnection.Connect.Success", - "description": "Connection Success.", + "description": "Connection succeeded.", "objectEncoding": 3, }) if err = self.writeCommandMsgEnd(3, 0); err != nil { @@ -425,13 +429,6 @@ func (self *Conn) determineType() (err error) { return } - // > Stream Is Recored - w = self.writeUserControlMsgStart(eventtypeStreamIsRecorded) - w.WriteU32BE(self.avmsgsid) - if err = self.writeUserControlMsgEnd(); err != nil { - return - } - // > |RtmpSampleAccess() w = self.writeDataMsgStart() flvio.WriteAMF0Val(w, "|RtmpSampleAccess") @@ -784,7 +781,7 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { } func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { - metadata := flvio.AMFECMAArray{} + metadata := flvio.AMFMap{} for _, _stream := range streams { typ := _stream.Type() @@ -1420,22 +1417,50 @@ func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) { return h.Sum(nil) } -func hsCalcDigestPos(p []byte, off int, mod int, add int) (pos int) { +func hsCalcDigestPos(p []byte, base int) (pos int) { for i := 0; i < 4; i++ { - pos += int(p[i+off]) + pos += int(p[base+i]) } - pos = pos%mod+add + pos = (pos%728)+base+4 return } -func hsCreateC1(p []byte) { - p[4] = 9 - p[5] = 0 - p[6] = 124 - p[7] = 2 - rand.Read(p[8:]) - gap := hsCalcDigestPos(p, 8, 728, 12) - digest := hsMakeDigest(hsClientPartialKey, p, gap) +func hsFindDigest(p []byte, key []byte, base int) int { + gap := hsCalcDigestPos(p, base) + digest := hsMakeDigest(key, p, gap) + if bytes.Compare(p[gap:gap+32], digest) != 0 { + return -1 + } + return gap +} + +func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []byte) { + var pos int + if pos = hsFindDigest(p, peerkey, 772); pos == -1 { + if pos = hsFindDigest(p, peerkey, 8); pos == -1 { + return + } + } + ok = true + digest = hsMakeDigest(key, p[pos:pos+32], -1) + return +} + +func hsCreate01(p []byte, time uint32, ver uint32, key []byte) { + p[0] = 3 + p1 := p[1:] + rand.Read(p1[8:]) + pio.PutU32BE(p1[0:4], time) + pio.PutU32BE(p1[4:8], ver) + gap := hsCalcDigestPos(p1, 8) + digest := hsMakeDigest(key, p1, gap) + copy(p1[gap:], digest) +} + +func hsCreate2(p []byte, key []byte) { + rand.Read(p) + gap := len(p)-32 + digest := hsMakeDigest(key, p, gap) copy(p[gap:], digest) } @@ -1444,7 +1469,7 @@ func (self *Conn) handshakeClient() (err error) { C0C1C2 := random[:1536*2+1] C0 := C0C1C2[:1] - C1 := C0C1C2[1:1536+1] + //C1 := C0C1C2[1:1536+1] C0C1 := C0C1C2[:1536+1] C2 := C0C1C2[1536+1:] @@ -1455,7 +1480,7 @@ func (self *Conn) handshakeClient() (err error) { //S2 := S0S1S2[1536+1:] C0[0] = 3 - hsCreateC1(C1) + //hsCreate01(C0C1, hsClientFullKey) // > C0C1 if _, err = self.bw.Write(C0C1); err != nil { @@ -1474,11 +1499,8 @@ func (self *Conn) handshakeClient() (err error) { fmt.Println("rtmp: handshakeClient: server version", S1[4],S1[5],S1[6],S1[7]) } - if S1[4] >= 3 { - // TODO + if ver := pio.GetU32BE(S1[4:8]); ver != 0 { C2 = S1 - //err = fmt.Errorf("rtmp: newstyle handshake unspported") - //return } else { C2 = S1 } @@ -1504,9 +1526,9 @@ func (self *Conn) handshakeServer() (err error) { C2 := C0C1C2[1536+1:] S0S1S2 := random[1536*2+1:] - S0 := S0S1S2[:1] + //S0 := S0S1S2[:1] S1 := S0S1S2[1:1536+1] - //S0S1 := S0S1S2[:1536+1] + S0S1 := S0S1S2[:1536+1] S2 := S0S1S2[1536+1:] // < C0C1 @@ -1518,16 +1540,24 @@ func (self *Conn) handshakeServer() (err error) { return } - if false { - epoch := pio.GetU32BE(C1[0:4]) - fmt.Println("rtmp: handshake client epoch", epoch) - } + clitime := pio.GetU32BE(C1[0:4]) + srvtime := clitime + srvver := uint32(0x0d0e0a0d) + cliver := pio.GetU32BE(C1[4:8]) - S0[0] = 3 - copy(S1[0:4], C1[0:4]) - rand.Read(S1[8:]) - copy(S2[0:4], C1[0:4]) - copy(S2[8:], C1[8:]) + if cliver != 0 { + var ok bool + var digest []byte + if ok, digest = hsParse1(C1, hsClientPartialKey, hsServerFullKey); !ok { + err = fmt.Errorf("rtmp: handshake server: C1 invalid") + return + } + hsCreate01(S0S1, srvtime, srvver, hsServerPartialKey) + hsCreate2(S2, digest) + } else { + copy(S1, C1) + copy(S2, C2) + } // > S0S1S2 if _, err = self.bw.Write(S0S1S2); err != nil { From 8493b20b5e1af1a3edae85da107c4006b372e16f Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 13:11:29 +0800 Subject: [PATCH 19/24] rename --- server.go => rtmp.go | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) rename server.go => rtmp.go (98%) diff --git a/server.go b/rtmp.go similarity index 98% rename from server.go rename to rtmp.go index 43a973d..85e8d09 100644 --- a/server.go +++ b/rtmp.go @@ -142,6 +142,7 @@ func (self *Server) ListenAndServe() (err error) { type stream struct { codec av.CodecData + lasttime time.Duration } func newStream(codec av.CodecData) *stream { @@ -972,28 +973,30 @@ func (self *Conn) writeChunks(csid uint32, timestamp uint32, msgtypeid uint8, ms msghdrtype := 0 var tsdelta uint32 - cs := self.writecsmap[csid] - if cs == nil { - cs = &chunkStream{} - self.writecsmap[csid] = cs - } else { - if msgsid == cs.msgsid { - if uint32(msgdatalen) == cs.msgdatalen && msgtypeid == cs.msgtypeid { - if timestamp == cs.timenow { - msghdrtype = 3 + if false { // always msghdrtype==1 is ok + cs := self.writecsmap[csid] + if cs == nil { + cs = &chunkStream{} + self.writecsmap[csid] = cs + } else { + if msgsid == cs.msgsid { + if uint32(msgdatalen) == cs.msgdatalen && msgtypeid == cs.msgtypeid { + if timestamp == cs.timenow { + msghdrtype = 3 + } else { + msghdrtype = 2 + } } else { - msghdrtype = 2 + msghdrtype = 1 } - } else { - msghdrtype = 1 } + tsdelta = timestamp - cs.timenow } - tsdelta = timestamp - cs.timenow + cs.timenow = timestamp + cs.msgdatalen = uint32(msgdatalen) + cs.msgtypeid = msgtypeid + cs.msgsid = msgsid } - cs.timenow = timestamp - cs.msgdatalen = uint32(msgdatalen) - cs.msgtypeid = msgtypeid - cs.msgsid = msgsid if err = self.bw.WriteU8(byte(csid)&0x3f|byte(msghdrtype)<<6); err != nil { return From 57c2e849f2c042555c4521477ff0a388cfeb7d35 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 13:14:11 +0800 Subject: [PATCH 20/24] set default write chunk size to 1024*1024*128 --- rtmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp.go b/rtmp.go index 85e8d09..87007f7 100644 --- a/rtmp.go +++ b/rtmp.go @@ -324,7 +324,7 @@ func (self *Conn) determineType() (err error) { if err = self.writeSetPeerBandwidth(5000000, 2); err != nil { return } - self.writeMaxChunkSize = 4000 + self.writeMaxChunkSize = 1024*1024*128 // > SetChunkSize if err = self.writeSetChunkSize(uint32(self.writeMaxChunkSize)); err != nil { return From 29c2b0e6af6481af8a589b3cda331cbb71c8c3a8 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 19:17:58 +0800 Subject: [PATCH 21/24] add handler --- rtmp.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rtmp.go b/rtmp.go index 87007f7..a64912d 100644 --- a/rtmp.go +++ b/rtmp.go @@ -14,6 +14,7 @@ import ( "github.com/nareix/pio" "github.com/nareix/flv/flvio" "github.com/nareix/av" + "github.com/nareix/av/avutil" "github.com/nareix/codec" "github.com/nareix/codec/h264parser" "github.com/nareix/codec/aacparser" @@ -1578,3 +1579,14 @@ func (self *Conn) handshakeServer() (err error) { return } +func Handler(h *avutil.RegisterHandler) { + h.UrlDemuxer = func(uri string) (ok bool, demuxer av.DemuxCloser, err error) { + if !strings.HasPrefix(uri, "rtmp://") { + return + } + ok = true + demuxer, err = Dial(uri) + return + } +} + From f8b8ea404ceb518c054c3c514f155c7b73090cd7 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 22:52:12 +0800 Subject: [PATCH 22/24] ReadHeader() -> Streams() --- rtmp.go | 104 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/rtmp.go b/rtmp.go index a64912d..cbd3b6b 100644 --- a/rtmp.go +++ b/rtmp.go @@ -38,16 +38,6 @@ func ParseURL(uri string) (host string, path string) { return } -func Open(uri string) (conn *Conn, err error) { - if conn, err = Dial(uri); err != nil { - return - } - if err = conn.ReadHeader(); err != nil { - return - } - return -} - func Dial(uri string) (conn *Conn, err error) { return DialTimeout(uri, 0) } @@ -75,10 +65,7 @@ type Server struct { } func (self *Server) handleConn(conn *Conn) (err error) { - if err = conn.handshakeServer(); err != nil { - return - } - if err = conn.determineType(); err != nil { + if err = conn.prepare(stageCommandDone, 0); err != nil { return } @@ -152,6 +139,17 @@ func newStream(codec av.CodecData) *stream { } } +const ( + stageHandshakeDone = iota+1 + stageCommandDone + stageCodecDataDone +) + +const ( + prepareReading = iota+1 + prepareWriting +) + type Conn struct { Host string Path string @@ -177,6 +175,7 @@ type Conn struct { isserver bool publishing, playing bool reading, writing bool + stage int avmsgsid uint32 @@ -301,7 +300,7 @@ func formatPath(app, play string) string { return strings.Join(out, "/") } -func (self *Conn) determineType() (err error) { +func (self *Conn) recvConnect() (err error) { var connectpath string // < connect("app") @@ -398,6 +397,7 @@ func (self *Conn) determineType() (err error) { self.Path = formatPath(connectpath, publishpath) self.publishing = true self.reading = true + self.stage++ return // < play("path") @@ -443,6 +443,7 @@ func (self *Conn) determineType() (err error) { self.Path = formatPath(connectpath, playpath) self.playing = true self.writing = true + self.stage++ return } @@ -573,6 +574,7 @@ func (self *Conn) probe() (err error) { return } + self.stage++ return } @@ -676,10 +678,15 @@ func (self *Conn) connectPlay() (err error) { self.reading = true self.playing = true + self.stage++ return } func (self *Conn) ReadPacket() (pkt av.Packet, err error) { + if err = self.prepare(stageCodecDataDone, prepareReading); err != nil { + return + } + poll: for { var _tag flvio.Tag @@ -717,26 +724,54 @@ func (self *Conn) ReadPacket() (pkt av.Packet, err error) { return } -func (self *Conn) ReadHeader() (err error) { - if !self.reading && !self.writing { - if err = self.handshakeClient(); err != nil { - return - } - if err = self.connectPlay(); err != nil { - return - } - if err = self.probe(); err != nil { - return - } - } else if self.publishing && self.reading { - if err = self.probe(); err != nil { - return +func (self *Conn) prepare(stage int, flags int) (err error) { + for self.stage < stage { + switch self.stage { + case 0: + if self.isserver { + if err = self.handshakeServer(); err != nil { + return + } + } else { + if err = self.handshakeClient(); err != nil { + return + } + } + + case stageHandshakeDone: + if self.isserver { + if err = self.recvConnect(); err != nil { + return + } + } else { + if flags == prepareReading { + if err = self.connectPlay(); err != nil { + return + } + } else { + err = fmt.Errorf("rtmp: connect publish unsupported") + return + } + } + + case stageCommandDone: + if flags == prepareReading { + if err = self.probe(); err != nil { + return + } + } else { + err = fmt.Errorf("rtmp: call WriteHeader() before WritePacket()") + return + } } } return } func (self *Conn) Streams() (streams []av.CodecData, err error) { + if err = self.prepare(stageCodecDataDone, prepareReading); err != nil { + return + } for _, stream := range self.streams { streams = append(streams, stream.codec) } @@ -752,6 +787,10 @@ func tsToTime(ts uint32) time.Duration { } func (self *Conn) WritePacket(pkt av.Packet) (err error) { + if err = self.prepare(stageCodecDataDone, prepareWriting); err != nil { + return + } + stream := self.streams[pkt.Idx] ts := timeToTs(pkt.Time) @@ -783,6 +822,10 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { } func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { + if err = self.prepare(stageCommandDone, prepareWriting); err != nil { + return + } + metadata := flvio.AMFMap{} for _, _stream := range streams { @@ -853,6 +896,7 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) { } } + self.stage++ return } @@ -1517,6 +1561,7 @@ func (self *Conn) handshakeClient() (err error) { return } + self.stage++ return } @@ -1576,6 +1621,7 @@ func (self *Conn) handshakeServer() (err error) { return } + self.stage++ return } From ebdf95b174453d1824e41a711fa3200425d51d18 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 18:43:27 +0800 Subject: [PATCH 23/24] use h264parser.CheckNALUsType for muxer and demuxer --- rtmp.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/rtmp.go b/rtmp.go index cbd3b6b..c1a2d79 100644 --- a/rtmp.go +++ b/rtmp.go @@ -708,7 +708,11 @@ func (self *Conn) ReadPacket() (pkt av.Packet, err error) { switch tag := _tag.(type) { case *flvio.Videodata: pkt.CompositionTime = tsToTime(uint32(tag.CompositionTime)) - pkt.Data = tag.Data + if typ := h264parser.CheckNALUsType(tag.Data); typ != h264parser.NALU_AVCC { + err = fmt.Errorf("rtmp: input h264 nalu format=%d invalid", typ) + return + } + pkt.Data = tag.Data[4:] pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY pkt.Idx = int8(self.videostreamidx) break poll @@ -809,7 +813,14 @@ func (self *Conn) WritePacket(pkt av.Packet) (err error) { } case av.H264: - videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.IsKeyFrame, pkt.Data) + if typ := h264parser.CheckNALUsType(pkt.Data); typ != h264parser.NALU_RAW { + err = fmt.Errorf("rtmp: h264 nalu format=%d invalid", typ) + return + } + var b [4]byte + pio.PutU32BE(b[:], uint32(len(pkt.Data))) + videodata := self.makeH264Videodata(flvio.AVC_NALU, pkt.IsKeyFrame, []byte{}) + videodata.Datav = [][]byte{b[:], pkt.Data} videodata.CompositionTime = int32(timeToTs(pkt.CompositionTime)) w := self.writeVideoDataStart() videodata.Marshal(w) From 88373387b8cf909bdfd2c3f04363703929b481a2 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:20:07 +0800 Subject: [PATCH 24/24] change to h264parser.FindDataNALUInAVCCNALUs() --- rtmp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtmp.go b/rtmp.go index c1a2d79..1790f4a 100644 --- a/rtmp.go +++ b/rtmp.go @@ -708,11 +708,11 @@ func (self *Conn) ReadPacket() (pkt av.Packet, err error) { switch tag := _tag.(type) { case *flvio.Videodata: pkt.CompositionTime = tsToTime(uint32(tag.CompositionTime)) - if typ := h264parser.CheckNALUsType(tag.Data); typ != h264parser.NALU_AVCC { - err = fmt.Errorf("rtmp: input h264 nalu format=%d invalid", typ) + var ok bool + if pkt.Data, ok = h264parser.FindDataNALUInAVCCNALUs(tag.Data); !ok { + err = fmt.Errorf("rtmp: input h264 format invalid") return } - pkt.Data = tag.Data[4:] pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY pkt.Idx = int8(self.videostreamidx) break poll