├── .gitignore ├── README.md ├── amf.go ├── amf_test.go ├── handshake.go ├── msg.go ├── server.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rtmp 2 | ==== 3 | 4 | Golang rtmp server 5 | 6 | Run a simple server 7 | 8 | package main 9 | 10 | import "github.com/go-av/rtmp" 11 | 12 | func main() { 13 | rtmp.SimpleServer() 14 | } 15 | 16 | Use avconv to publish stream 17 | 18 | avconv -re -i a.mp4 -c:a copy -c:v copy -f flv rtmp://localhost/myapp/1 19 | 20 | Use avplay to play stream 21 | 22 | avplay rtmp://localhost/myapp/1 23 | 24 | -------------------------------------------------------------------------------- /amf.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "io" 6 | "encoding/binary" 7 | ) 8 | 9 | var ( 10 | AMF_NUMBER = 0x00 11 | AMF_BOOLEAN = 0x01 12 | AMF_STRING = 0x02 13 | AMF_OBJECT = 0x03 14 | AMF_NULL = 0x05 15 | AMF_ARRAY_NULL = 0x06 16 | AMF_MIXED_ARRAY = 0x08 17 | AMF_END = 0x09 18 | AMF_ARRAY = 0x0a 19 | 20 | AMF_INT8 = 0x0100 21 | AMF_INT16 = 0x0101 22 | AMF_INT32 = 0x0102 23 | AMF_VARIANT_ = 0x0103 24 | ) 25 | 26 | type AMFObj struct { 27 | atype int 28 | str string 29 | i int 30 | buf []byte 31 | obj map[string]AMFObj 32 | f64 float64 33 | } 34 | 35 | func ReadAMF(r io.Reader) (a AMFObj) { 36 | a.atype = ReadInt(r, 1) 37 | switch (a.atype) { 38 | case AMF_STRING: 39 | n := ReadInt(r, 2) 40 | b := ReadBuf(r, n) 41 | a.str = string(b) 42 | case AMF_NUMBER: 43 | binary.Read(r, binary.BigEndian, &a.f64) 44 | case AMF_BOOLEAN: 45 | a.i = ReadInt(r, 1) 46 | case AMF_MIXED_ARRAY: 47 | ReadInt(r, 4) 48 | fallthrough 49 | case AMF_OBJECT: 50 | a.obj = map[string]AMFObj{} 51 | for { 52 | n := ReadInt(r, 2) 53 | if n == 0 { 54 | break 55 | } 56 | name := string(ReadBuf(r, n)) 57 | a.obj[name] = ReadAMF(r) 58 | } 59 | case AMF_ARRAY, AMF_VARIANT_: 60 | panic("amf: read: unsupported array or variant") 61 | case AMF_INT8: 62 | a.i = ReadInt(r, 1) 63 | case AMF_INT16: 64 | a.i = ReadInt(r, 2) 65 | case AMF_INT32: 66 | a.i = ReadInt(r, 4) 67 | } 68 | return 69 | } 70 | 71 | func WriteAMF(r io.Writer, a AMFObj) { 72 | WriteInt(r, a.atype, 1) 73 | switch (a.atype) { 74 | case AMF_STRING: 75 | WriteInt(r, len(a.str), 2) 76 | r.Write([]byte(a.str)) 77 | case AMF_NUMBER: 78 | binary.Write(r, binary.BigEndian, a.f64) 79 | case AMF_BOOLEAN: 80 | WriteInt(r, a.i, 1) 81 | case AMF_MIXED_ARRAY: 82 | r.Write(a.buf[:4]) 83 | case AMF_OBJECT: 84 | for name, val := range a.obj { 85 | WriteInt(r, len(name), 2) 86 | r.Write([]byte(name)) 87 | WriteAMF(r, val) 88 | } 89 | WriteInt(r, 9, 3) 90 | case AMF_ARRAY, AMF_VARIANT_: 91 | panic("amf: write unsupported array, var") 92 | case AMF_INT8: 93 | WriteInt(r, a.i, 1) 94 | case AMF_INT16: 95 | WriteInt(r, a.i, 2) 96 | case AMF_INT32: 97 | WriteInt(r, a.i, 4) 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /amf_test.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "testing" 6 | "encoding/base64" 7 | "bytes" 8 | "fmt" 9 | ) 10 | 11 | var ( 12 | data = `AgAHY29ubmVjdAA/8AAAAAAAAAMAA2FwcAIABW15YXBwAAhmbGFzaFZlcgIAEE1BQyAxMSw1LDUwMiwxNDkABnN3ZlVybAIAJmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MS9zd2YvandwbGF5ZXIuc3dmAAV0Y1VybAIAFnJ0bXA6Ly9sb2NhbGhvc3QvbXlhcHAABGZwYWQBAAAMY2FwYWJpbGl0aWVzAEBt4AAAAAAAAAthdWRpb0NvZGVjcwBAq+4AAAAAAAALdmlkZW9Db2RlY3MAQG+AAAAAAAAADXZpZGVvRnVuY3Rpb24AP/AAAAAAAAAAB3BhZ2VVcmwCABpodHRwOi8vbG9jYWxob3N0OjgwODEvc3dmLwAOb2JqZWN0RW5jb2RpbmcAAAAAAAAAAAAAAAk=` 13 | ) 14 | 15 | func TestHal(t *testing.T) { 16 | dec := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(data)) 17 | r := NewAMFReader(dec) 18 | obj := r.ReadAMF() 19 | fmt.Printf("%v\n", obj) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /handshake.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "io" 6 | "crypto/hmac" 7 | "crypto/sha256" 8 | "bytes" 9 | "math/rand" 10 | ) 11 | 12 | var ( 13 | clientKey = []byte{ 14 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 15 | 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', 16 | '0', '0', '1', 17 | 18 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 19 | 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 20 | 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, 21 | } 22 | serverKey = []byte{ 23 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 24 | 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', 25 | 'S', 'e', 'r', 'v', 'e', 'r', ' ', 26 | '0', '0', '1', 27 | 28 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 29 | 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 30 | 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE, 31 | } 32 | clientKey2 = clientKey[:30] 33 | serverKey2 = serverKey[:36] 34 | serverVersion = []byte{ 35 | 0x0D, 0x0E, 0x0A, 0x0D, 36 | } 37 | ) 38 | 39 | func makeDigest(key []byte, src []byte, skip int) (dst []byte) { 40 | h := hmac.New(sha256.New, key) 41 | if skip >= 0 && skip < len(src) { 42 | if skip != 0 { 43 | h.Write(src[:skip]) 44 | } 45 | if len(src) != skip + 32 { 46 | h.Write(src[skip+32:]) 47 | } 48 | } else { 49 | h.Write(src) 50 | } 51 | return h.Sum(nil) 52 | } 53 | 54 | func findDigest(b []byte, key []byte, base int) (int) { 55 | offs := 0 56 | for n := 0; n < 4; n++ { 57 | offs += int(b[base + n]) 58 | } 59 | offs = (offs % 728) + base + 4 60 | // fmt.Printf("offs %v\n", offs) 61 | dig := makeDigest(key, b, offs) 62 | // fmt.Printf("digest %v\n", digest) 63 | // fmt.Printf("p %v\n", b[offs:offs+32]) 64 | if bytes.Compare(b[offs:offs+32], dig) != 0 { 65 | offs = -1 66 | } 67 | return offs 68 | } 69 | 70 | func writeDigest(b []byte, key []byte, base int) { 71 | offs := 0 72 | for n := 8; n < 12; n++ { 73 | offs += int(b[base + n]) 74 | } 75 | offs = (offs % 728) + base + 12 76 | 77 | dig := makeDigest(key, b, offs) 78 | copy(b[offs:], dig) 79 | } 80 | 81 | func createChal(b []byte, ver []byte, key []byte) { 82 | b[0] = 3 83 | copy(b[5:9], ver) 84 | for i := 9; i < 1537; i++ { 85 | b[i] = byte(rand.Int() % 256) 86 | } 87 | writeDigest(b[1:], key, 0) 88 | } 89 | 90 | func createResp(b []byte, key []byte) { 91 | for i := 0; i < 1536; i++ { 92 | b[i] = byte(rand.Int() % 256) 93 | } 94 | dig := makeDigest(key, b, 1536-32) 95 | copy(b[1536-32:], dig) 96 | } 97 | 98 | func parseChal(b []byte, peerKey []byte, key []byte) (dig []byte, err int) { 99 | if b[0] != 0x3 { 100 | l.Printf("handshake: invalid rtmp version") 101 | err = 1 102 | return 103 | } 104 | 105 | epoch := b[1:5] 106 | ver := b[5:9] 107 | l.Printf("handshake: epoch %v ver %v", epoch, ver) 108 | 109 | var offs int 110 | if offs = findDigest(b[1:], peerKey, 772); offs == -1 { 111 | if offs = findDigest(b[1:], peerKey, 8); offs == -1 { 112 | l.Printf("handshake: digest not found") 113 | err = 1 114 | return 115 | } 116 | } 117 | 118 | l.Printf("handshake: offs = %v", offs) 119 | 120 | dig = makeDigest(key, b[1+offs:1+offs+32], -1) 121 | return 122 | } 123 | 124 | 125 | func handShake(rw io.ReadWriter) { 126 | b := ReadBuf(rw, 1537) 127 | l.Printf("handshake: got client chal") 128 | dig, err := parseChal(b, clientKey2, serverKey) 129 | if err != 0 { 130 | return 131 | } 132 | 133 | createChal(b, serverVersion, serverKey2) 134 | l.Printf("handshake: send server chal") 135 | rw.Write(b) 136 | 137 | b = make([]byte, 1536) 138 | createResp(b, dig) 139 | l.Printf("handshake: send server resp") 140 | rw.Write(b) 141 | 142 | b = ReadBuf(rw, 1536) 143 | l.Printf("handshake: got client resp") 144 | } 145 | 146 | -------------------------------------------------------------------------------- /msg.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "io" 6 | "bytes" 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | var ( 12 | MSG_CHUNK_SIZE = 1 13 | MSG_ABORT = 2 14 | MSG_ACK = 3 15 | MSG_USER = 4 16 | MSG_ACK_SIZE = 5 17 | MSG_BANDWIDTH = 6 18 | MSG_EDGE = 7 19 | MSG_AUDIO = 8 20 | MSG_VIDEO = 9 21 | MSG_AMF3_META = 15 22 | MSG_AMF3_SHARED = 16 23 | MSG_AMF3_CMD = 17 24 | MSG_AMF_META = 18 25 | MSG_AMF_SHARED = 19 26 | MSG_AMF_CMD = 20 27 | MSG_AGGREGATE = 22 28 | MSG_MAX = 22 29 | ) 30 | 31 | var ( 32 | MsgTypeStr = []string { 33 | "?", 34 | "CHUNK_SIZE", "ABORT", "ACK", 35 | "USER", "ACK_SIZE", "BANDWIDTH", "EDGE", 36 | "AUDIO", "VIDEO", 37 | "AMF3_META", "AMF3_SHARED", "AFM3_CMD", 38 | "AMF_META", "AMF_SHARED", "AMF_CMD", 39 | "AGGREGATE", 40 | } 41 | ) 42 | 43 | type chunkHeader struct { 44 | typeid int 45 | mlen int 46 | csid int 47 | cfmt int 48 | ts int 49 | tsdelta int 50 | strid int 51 | } 52 | 53 | func readChunkHeader (r io.Reader) (m chunkHeader) { 54 | i := ReadInt(r, 1) 55 | m.cfmt = (i>>6)&3; 56 | m.csid = i&0x3f; 57 | 58 | if m.csid == 0 { 59 | j := ReadInt(r, 1) 60 | m.csid = j + 64 61 | } 62 | 63 | if m.csid == 0x3f { 64 | j := ReadInt(r, 2) 65 | m.csid = j + 64 66 | } 67 | 68 | if m.cfmt == 0 { 69 | m.ts = ReadInt(r, 3) 70 | m.mlen = ReadInt(r, 3) 71 | m.typeid = ReadInt(r, 1) 72 | m.strid = ReadIntLE(r, 4) 73 | } 74 | 75 | if m.cfmt == 1 { 76 | m.tsdelta = ReadInt(r, 3) 77 | m.mlen = ReadInt(r, 3) 78 | m.typeid = ReadInt(r, 1) 79 | } 80 | 81 | if m.cfmt == 2 { 82 | m.tsdelta = ReadInt(r, 3) 83 | } 84 | 85 | if m.ts == 0xffffff { 86 | m.ts = ReadInt(r, 4) 87 | } 88 | if m.tsdelta == 0xffffff { 89 | m.tsdelta = ReadInt(r, 4) 90 | } 91 | 92 | //l.Printf("chunk: %v", m) 93 | 94 | return 95 | } 96 | 97 | const ( 98 | UNKNOWN = 0 99 | PLAYER = 1 100 | PUBLISHER = 2 101 | ) 102 | 103 | const ( 104 | WAIT_EXTRA = 0 105 | WAIT_DATA = 1 106 | ) 107 | 108 | 109 | type MsgStream struct { 110 | r stream 111 | Msg map[int]*Msg 112 | vts, ats int 113 | 114 | meta AMFObj 115 | id string 116 | role int 117 | stat int 118 | app string 119 | W,H int 120 | strid int 121 | extraA, extraV []byte 122 | que chan *Msg 123 | l *log.Logger 124 | } 125 | 126 | type Msg struct { 127 | chunkHeader 128 | data *bytes.Buffer 129 | 130 | key bool 131 | curts int 132 | } 133 | 134 | func (m *Msg) String() string { 135 | var typestr string 136 | if m.typeid < len(MsgTypeStr) { 137 | typestr = MsgTypeStr[m.typeid] 138 | } else { 139 | typestr = "?" 140 | } 141 | return fmt.Sprintf("%s %d %v", typestr, m.mlen, m.chunkHeader) 142 | } 143 | 144 | var ( 145 | mrseq = 0 146 | ) 147 | 148 | func NewMsgStream(r io.ReadWriteCloser) *MsgStream { 149 | mrseq++ 150 | return &MsgStream{ 151 | r:stream{r}, 152 | Msg:map[int]*Msg{}, 153 | id:fmt.Sprintf("#%d", mrseq), 154 | } 155 | } 156 | 157 | func (mr *MsgStream) String() string { 158 | return mr.id 159 | } 160 | 161 | func (mr *MsgStream) Close() { 162 | mr.r.Close() 163 | } 164 | 165 | func (r *MsgStream) WriteMsg(cfmt, csid, typeid, strid, ts int, data []byte) { 166 | var b bytes.Buffer 167 | start := 0 168 | for i := 0; start < len(data); i++ { 169 | if i == 0 { 170 | if cfmt == 0 { 171 | WriteInt(&b, csid, 1) // fmt=0 csid 172 | WriteInt(&b, ts, 3) // ts 173 | WriteInt(&b, len(data), 3) // message length 174 | WriteInt(&b, typeid, 1) // message type id 175 | WriteIntLE(&b, strid, 4) // message stream id 176 | } else { 177 | WriteInt(&b, 0x1<<6 + csid, 1) // fmt=1 csid 178 | WriteInt(&b, ts, 3) // tsdelta 179 | WriteInt(&b, len(data), 3) // message length 180 | WriteInt(&b, typeid, 1) // message type id 181 | } 182 | } else { 183 | WriteBuf(&b, []byte{0x3<<6 + byte(csid)}) // fmt=3, csid 184 | } 185 | size := 128 186 | if len(data) - start < size { 187 | size = len(data) - start 188 | } 189 | WriteBuf(&b, data[start:start+size]) 190 | WriteBuf(r.r, b.Bytes()) 191 | b.Reset() 192 | start += size 193 | } 194 | l.Printf("Msg: csid %d ts %d paylen %d", csid, ts, len(data)) 195 | } 196 | 197 | func (r *MsgStream) WriteAudio(strid, ts int, data []byte) { 198 | d := append([]byte{0xaf, 1}, data...) 199 | tsdelta := ts - r.ats 200 | r.ats = ts 201 | r.WriteMsg(1, 7, MSG_AUDIO, strid, tsdelta, d) 202 | } 203 | 204 | func (r *MsgStream) WriteAAC(strid, ts int, data []byte) { 205 | d := append([]byte{0xaf, 0}, data...) 206 | r.ats = ts 207 | r.WriteMsg(0, 7, MSG_AUDIO, strid, ts, d) 208 | } 209 | 210 | func (r *MsgStream) WriteVideo(strid,ts int, key bool, data []byte) { 211 | var b int 212 | if key { 213 | b = 0x17 214 | } else { 215 | b = 0x27 216 | } 217 | d := append([]byte{byte(b), 1, 0, 0, 0x50}, data...) 218 | tsdelta := ts - r.vts 219 | r.vts = ts 220 | r.WriteMsg(1, 6, MSG_VIDEO, strid, tsdelta, d) 221 | } 222 | 223 | func (r *MsgStream) WritePPS(strid, ts int, data []byte) { 224 | d := append([]byte{0x17, 0, 0, 0, 0}, data...) 225 | r.vts = ts 226 | r.WriteMsg(0, 6, MSG_VIDEO, strid, ts, d) 227 | } 228 | 229 | func (r *MsgStream) WriteAMFMeta(csid, strid int, a []AMFObj) { 230 | var b bytes.Buffer 231 | for _, v := range a { 232 | WriteAMF(&b, v) 233 | } 234 | r.WriteMsg(0, csid, MSG_AMF_META, strid, 0, b.Bytes()) 235 | } 236 | 237 | func (r *MsgStream) WriteAMFCmd(csid, strid int, a []AMFObj) { 238 | var b bytes.Buffer 239 | for _, v := range a { 240 | WriteAMF(&b, v) 241 | } 242 | r.WriteMsg(0, csid, MSG_AMF_CMD, strid, 0, b.Bytes()) 243 | } 244 | 245 | func (r *MsgStream) WriteMsg32(csid, typeid, strid, v int) { 246 | var b bytes.Buffer 247 | WriteInt(&b, v, 4) 248 | r.WriteMsg(0, csid, typeid, strid, 0, b.Bytes()) 249 | } 250 | 251 | func (r *MsgStream) ReadMsg() *Msg { 252 | ch := readChunkHeader(r.r) 253 | m, ok := r.Msg[ch.csid] 254 | if !ok { 255 | //l.Printf("chunk: new") 256 | m = &Msg{ch, &bytes.Buffer{}, false, 0} 257 | r.Msg[ch.csid] = m 258 | } 259 | 260 | switch ch.cfmt { 261 | case 0: 262 | m.ts = ch.ts 263 | m.mlen = ch.mlen 264 | m.typeid = ch.typeid 265 | m.curts = m.ts 266 | case 1: 267 | m.tsdelta = ch.tsdelta 268 | m.mlen = ch.mlen 269 | m.typeid = ch.typeid 270 | m.curts += m.tsdelta 271 | case 2: 272 | m.tsdelta = ch.tsdelta 273 | } 274 | 275 | left := m.mlen - m.data.Len() 276 | size := 128 277 | if size > left { 278 | size = left 279 | } 280 | //l.Printf("chunk: %v", m) 281 | if size > 0 { 282 | io.CopyN(m.data, r.r, int64(size)) 283 | } 284 | 285 | if size == left { 286 | rm := new(Msg) 287 | *rm = *m 288 | l.Printf("event: fmt%d %v curts %d pre %v", ch.cfmt, m, m.curts, m.data.Bytes()[:9]) 289 | if m.typeid == MSG_VIDEO && int(m.data.Bytes()[0]) == 0x17 { 290 | rm.key = true 291 | } else { 292 | rm.key = false 293 | } 294 | m.data = &bytes.Buffer{} 295 | return rm 296 | } 297 | 298 | return nil 299 | } 300 | 301 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "bytes" 6 | "net" 7 | "fmt" 8 | "reflect" 9 | "io/ioutil" 10 | "os" 11 | "bufio" 12 | "log" 13 | "time" 14 | "strings" 15 | _ "runtime/debug" 16 | ) 17 | 18 | var ( 19 | event = make(chan eventS, 0) 20 | eventDone = make(chan int, 0) 21 | ) 22 | 23 | type eventS struct { 24 | id int 25 | mr *MsgStream 26 | m *Msg 27 | } 28 | 29 | type eventID int 30 | 31 | func (e eventS) String() string { 32 | switch e.id { 33 | case E_NEW: 34 | return "new" 35 | case E_PUBLISH: 36 | return "publish" 37 | case E_PLAY: 38 | return "play" 39 | case E_DATA: 40 | switch e.m.typeid { 41 | case MSG_VIDEO: 42 | return fmt.Sprintf("ts %d video %d bytes key %t", e.m.curts, e.m.data.Len(), e.m.key) 43 | case MSG_AUDIO: 44 | return fmt.Sprintf("ts %d audio %d bytes", e.m.curts, e.m.data.Len()) 45 | } 46 | case E_CLOSE: 47 | return "close" 48 | } 49 | return "" 50 | } 51 | 52 | /* 53 | server: 54 | connect 55 | createStream 56 | publish 57 | client: 58 | connect 59 | createStream 60 | getStreamLength 61 | play 62 | */ 63 | 64 | const ( 65 | E_NEW = iota 66 | E_PUBLISH 67 | E_PLAY 68 | E_DATA 69 | E_CLOSE 70 | ) 71 | 72 | func handleConnect(mr *MsgStream, trans float64, app string) { 73 | 74 | l.Printf("stream %v: connect: %s", mr, app) 75 | 76 | mr.app = app 77 | 78 | mr.WriteMsg32(2, MSG_ACK_SIZE, 0, 5000000) 79 | mr.WriteMsg32(2, MSG_BANDWIDTH, 0, 5000000) 80 | mr.WriteMsg32(2, MSG_CHUNK_SIZE, 0, 128) 81 | 82 | mr.WriteAMFCmd(3, 0, []AMFObj { 83 | AMFObj { atype : AMF_STRING, str : "_result", }, 84 | AMFObj { atype : AMF_NUMBER, f64 : trans, }, 85 | AMFObj { atype : AMF_OBJECT, 86 | obj : map[string] AMFObj { 87 | "fmtVer" : AMFObj { atype : AMF_STRING, str : "FMS/3,0,1,123", }, 88 | "capabilities" : AMFObj { atype : AMF_NUMBER, f64 : 31, }, 89 | }, 90 | }, 91 | AMFObj { atype : AMF_OBJECT, 92 | obj : map[string] AMFObj { 93 | "level" : AMFObj { atype : AMF_STRING, str : "status", }, 94 | "code" : AMFObj { atype : AMF_STRING, str : "NetConnection.Connect.Success", }, 95 | "description" : AMFObj { atype : AMF_STRING, str : "Connection Success.", }, 96 | "objectEncoding" : AMFObj { atype : AMF_NUMBER, f64 : 0, }, 97 | }, 98 | }, 99 | }) 100 | } 101 | 102 | func handleMeta(mr *MsgStream, obj AMFObj) { 103 | 104 | mr.meta = obj 105 | mr.W = int(obj.obj["width"].f64) 106 | mr.H = int(obj.obj["height"].f64) 107 | 108 | l.Printf("stream %v: meta video %dx%d", mr, mr.W, mr.H) 109 | } 110 | 111 | func handleCreateStream(mr *MsgStream, trans float64) { 112 | 113 | l.Printf("stream %v: createStream", mr) 114 | 115 | mr.WriteAMFCmd(3, 0, []AMFObj { 116 | AMFObj { atype : AMF_STRING, str : "_result", }, 117 | AMFObj { atype : AMF_NUMBER, f64 : trans, }, 118 | AMFObj { atype : AMF_NULL, }, 119 | AMFObj { atype : AMF_NUMBER, f64 : 1 }, 120 | }) 121 | } 122 | 123 | func handleGetStreamLength(mr *MsgStream, trans float64) { 124 | } 125 | 126 | func handlePublish(mr *MsgStream) { 127 | 128 | l.Printf("stream %v: publish", mr) 129 | 130 | mr.WriteAMFCmd(3, 0, []AMFObj { 131 | AMFObj { atype : AMF_STRING, str : "onStatus", }, 132 | AMFObj { atype : AMF_NUMBER, f64 : 0, }, 133 | AMFObj { atype : AMF_NULL, }, 134 | AMFObj { atype : AMF_OBJECT, 135 | obj : map[string] AMFObj { 136 | "level" : AMFObj { atype : AMF_STRING, str : "status", }, 137 | "code" : AMFObj { atype : AMF_STRING, str : "NetStream.Publish.Start", }, 138 | "description" : AMFObj { atype : AMF_STRING, str : "Start publising.", }, 139 | }, 140 | }, 141 | }) 142 | 143 | event <- eventS{id:E_PUBLISH, mr:mr} 144 | <-eventDone 145 | } 146 | 147 | type testsrc struct { 148 | r *bufio.Reader 149 | dir string 150 | w,h int 151 | ts int 152 | codec string 153 | key bool 154 | idx int 155 | data []byte 156 | } 157 | 158 | func tsrcNew() (m *testsrc) { 159 | m = &testsrc{} 160 | m.dir = "/pixies/go/data/tmp" 161 | fi, _ := os.Open(fmt.Sprintf("%s/index", m.dir)) 162 | m.r = bufio.NewReader(fi) 163 | l, _ := m.r.ReadString('\n') 164 | fmt.Sscanf(l, "%dx%d", &m.w, &m.h) 165 | return 166 | } 167 | 168 | func (m *testsrc) fetch() (err error) { 169 | l, err := m.r.ReadString('\n') 170 | if err != nil { 171 | return 172 | } 173 | a := strings.Split(l, ",") 174 | fmt.Sscanf(a[0], "%d", &m.ts) 175 | m.codec = a[1] 176 | fmt.Sscanf(a[2], "%d", &m.idx) 177 | switch m.codec { 178 | case "h264": 179 | fmt.Sscanf(a[3], "%t", &m.key) 180 | m.data, err = ioutil.ReadFile(fmt.Sprintf("%s/h264/%d.264", m.dir, m.idx)) 181 | case "aac": 182 | m.data, err = ioutil.ReadFile(fmt.Sprintf("%s/aac/%d.aac", m.dir, m.idx)) 183 | } 184 | return 185 | } 186 | 187 | func handlePlay(mr *MsgStream, strid int) { 188 | 189 | l.Printf("stream %v: play", mr) 190 | 191 | var tsrc *testsrc 192 | //tsrc = tsrcNew() 193 | 194 | if tsrc == nil { 195 | event <- eventS{id:E_PLAY, mr:mr} 196 | <-eventDone 197 | } else { 198 | l.Printf("stream %v: test play data in %s", mr, tsrc.dir) 199 | mr.W = tsrc.w 200 | mr.H = tsrc.h 201 | l.Printf("stream %v: test video %dx%d", mr, mr.W, mr.H) 202 | } 203 | 204 | begin := func () { 205 | 206 | var b bytes.Buffer 207 | WriteInt(&b, 0, 2) 208 | WriteInt(&b, strid, 4) 209 | mr.WriteMsg(0, 2, MSG_USER, 0, 0, b.Bytes()) // stream begin 1 210 | 211 | mr.WriteAMFCmd(5, strid, []AMFObj { 212 | AMFObj { atype : AMF_STRING, str : "onStatus", }, 213 | AMFObj { atype : AMF_NUMBER, f64 : 0, }, 214 | AMFObj { atype : AMF_NULL, }, 215 | AMFObj { atype : AMF_OBJECT, 216 | obj : map[string] AMFObj { 217 | "level" : AMFObj { atype : AMF_STRING, str : "status", }, 218 | "code" : AMFObj { atype : AMF_STRING, str : "NetStream.Play.Start", }, 219 | "description" : AMFObj { atype : AMF_STRING, str : "Start live.", }, 220 | }, 221 | }, 222 | }) 223 | 224 | l.Printf("stream %v: begin: video %dx%d", mr, mr.W, mr.H) 225 | 226 | mr.WriteAMFMeta(5, strid, []AMFObj { 227 | AMFObj { atype : AMF_STRING, str : "|RtmpSampleAccess", }, 228 | AMFObj { atype : AMF_BOOLEAN, i: 1, }, 229 | AMFObj { atype : AMF_BOOLEAN, i: 1, }, 230 | }) 231 | 232 | mr.meta.obj["Server"] = AMFObj { atype : AMF_STRING, str : "Golang Rtmp Server", } 233 | mr.meta.atype = AMF_OBJECT 234 | l.Printf("stream %v: %v", mr, mr.meta) 235 | mr.WriteAMFMeta(5, strid, []AMFObj { 236 | AMFObj { atype : AMF_STRING, str : "onMetaData", }, 237 | mr.meta, 238 | /* 239 | AMFObj { atype : AMF_OBJECT, 240 | obj : map[string] AMFObj { 241 | "Server" : AMFObj { atype : AMF_STRING, str : "Golang Rtmp Server", }, 242 | "width" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.W), }, 243 | "height" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.H), }, 244 | "displayWidth" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.W), }, 245 | "displayHeight" : AMFObj { atype : AMF_NUMBER, f64 : float64(mr.H), }, 246 | "duration" : AMFObj { atype : AMF_NUMBER, f64 : 0, }, 247 | "framerate" : AMFObj { atype : AMF_NUMBER, f64 : 25000, }, 248 | "videodatarate" : AMFObj { atype : AMF_NUMBER, f64 : 731, }, 249 | "videocodecid" : AMFObj { atype : AMF_NUMBER, f64 : 7, }, 250 | "audiodatarate" : AMFObj { atype : AMF_NUMBER, f64 : 122, }, 251 | "audiocodecid" : AMFObj { atype : AMF_NUMBER, f64 : 10, }, 252 | }, 253 | }, 254 | */ 255 | }) 256 | } 257 | 258 | end := func () { 259 | 260 | l.Printf("stream %v: end", mr) 261 | 262 | var b bytes.Buffer 263 | WriteInt(&b, 1, 2) 264 | WriteInt(&b, strid, 4) 265 | mr.WriteMsg(0, 2, MSG_USER, 0, 0, b.Bytes()) // stream eof 1 266 | 267 | mr.WriteAMFCmd(5, strid, []AMFObj { 268 | AMFObj { atype : AMF_STRING, str : "onStatus", }, 269 | AMFObj { atype : AMF_NUMBER, f64 : 0, }, 270 | AMFObj { atype : AMF_NULL, }, 271 | AMFObj { atype : AMF_OBJECT, 272 | obj : map[string] AMFObj { 273 | "level" : AMFObj { atype : AMF_STRING, str : "status", }, 274 | "code" : AMFObj { atype : AMF_STRING, str : "NetStream.Play.Stop", }, 275 | "description" : AMFObj { atype : AMF_STRING, str : "Stop live.", }, 276 | }, 277 | }, 278 | }) 279 | } 280 | 281 | if tsrc == nil { 282 | 283 | for { 284 | nr := 0 285 | 286 | for { 287 | m := <-mr.que 288 | if m == nil { 289 | break 290 | } 291 | //if nr == 0 && !m.key { 292 | // continue 293 | //} 294 | if nr == 0 { 295 | begin() 296 | l.Printf("stream %v: extra size %d %d", mr, len(mr.extraA), len(mr.extraV)) 297 | mr.WriteAAC(strid, 0, mr.extraA[2:]) 298 | mr.WritePPS(strid, 0, mr.extraV[5:]) 299 | } 300 | l.Printf("data %v: got %v curts %v", mr, m, m.curts) 301 | switch m.typeid { 302 | case MSG_AUDIO: 303 | mr.WriteAudio(strid, m.curts, m.data.Bytes()[2:]) 304 | case MSG_VIDEO: 305 | mr.WriteVideo(strid, m.curts, m.key, m.data.Bytes()[5:]) 306 | } 307 | nr++ 308 | } 309 | end() 310 | } 311 | 312 | } else { 313 | 314 | begin() 315 | 316 | lf, _ := os.Create("/tmp/rtmp.log") 317 | ll := log.New(lf, "", 0) 318 | 319 | starttm := time.Now() 320 | k := 0 321 | 322 | for { 323 | err := tsrc.fetch() 324 | if err != nil { 325 | panic(err) 326 | } 327 | switch tsrc.codec { 328 | case "h264": 329 | if tsrc.idx == 0 { 330 | mr.WritePPS(strid, 0, tsrc.data) 331 | } else { 332 | mr.WriteVideo(strid, tsrc.ts, tsrc.key, tsrc.data) 333 | } 334 | case "aac": 335 | if tsrc.idx == 0 { 336 | mr.WriteAAC(strid, 0, tsrc.data) 337 | } else { 338 | mr.WriteAudio(strid, tsrc.ts, tsrc.data) 339 | } 340 | } 341 | dur := time.Since(starttm).Nanoseconds() 342 | diff := tsrc.ts - 1000 - int(dur/1000000) 343 | if diff > 0 { 344 | time.Sleep(time.Duration(diff)*time.Millisecond) 345 | } 346 | l.Printf("data %v: ts %v dur %v diff %v", mr, tsrc.ts, int(dur/1000000), diff) 347 | ll.Printf("#%d %d,%s,%d %d", k, tsrc.ts, tsrc.codec, tsrc.idx, len(tsrc.data)) 348 | k++ 349 | } 350 | } 351 | } 352 | 353 | func serve(mr *MsgStream) { 354 | 355 | defer func() { 356 | if err := recover(); err != nil { 357 | event <- eventS{id:E_CLOSE, mr:mr} 358 | <-eventDone 359 | l.Printf("stream %v: closed %v", mr, err) 360 | //if err != "EOF" { 361 | // l.Printf("stream %v: %v", mr, string(debug.Stack())) 362 | //} 363 | } 364 | }() 365 | 366 | handShake(mr.r) 367 | 368 | // f, _ := os.Create("/tmp/pub.log") 369 | // mr.l = log.New(f, "", 0) 370 | 371 | for { 372 | m := mr.ReadMsg() 373 | if m == nil { 374 | continue 375 | } 376 | 377 | //l.Printf("stream %v: msg %v", mr, m) 378 | 379 | if m.typeid == MSG_AUDIO || m.typeid == MSG_VIDEO { 380 | // mr.l.Printf("%d,%d", m.typeid, m.data.Len()) 381 | event <- eventS{id:E_DATA, mr:mr, m:m} 382 | <-eventDone 383 | } 384 | 385 | if m.typeid == MSG_AMF_CMD || m.typeid == MSG_AMF_META { 386 | a := ReadAMF(m.data) 387 | //l.Printf("server: amfobj %v\n", a) 388 | switch a.str { 389 | case "connect": 390 | a2 := ReadAMF(m.data) 391 | a3 := ReadAMF(m.data) 392 | if _, ok := a3.obj["app"]; !ok || a3.obj["app"].str == "" { 393 | panic("connect: app not found") 394 | } 395 | handleConnect(mr, a2.f64, a3.obj["app"].str) 396 | case "@setDataFrame": 397 | ReadAMF(m.data) 398 | a3 := ReadAMF(m.data) 399 | handleMeta(mr, a3) 400 | l.Printf("stream %v: setdataframe", mr) 401 | case "createStream": 402 | a2 := ReadAMF(m.data) 403 | handleCreateStream(mr, a2.f64) 404 | case "publish": 405 | handlePublish(mr) 406 | case "play": 407 | handlePlay(mr, m.strid) 408 | } 409 | } 410 | } 411 | } 412 | 413 | func listenEvent() { 414 | idmap := map[string]*MsgStream{} 415 | pubmap := map[string]*MsgStream{} 416 | 417 | for { 418 | e := <-event 419 | if e.id == E_DATA { 420 | l.Printf("data %v: %v", e.mr, e) 421 | } else { 422 | l.Printf("event %v: %v", e.mr, e) 423 | } 424 | switch { 425 | case e.id == E_NEW: 426 | idmap[e.mr.id] = e.mr 427 | case e.id == E_PUBLISH: 428 | if _, ok := pubmap[e.mr.app]; ok { 429 | l.Printf("event %v: duplicated publish with %v app %s", e.mr, pubmap[e.mr.app], e.mr.app) 430 | e.mr.Close() 431 | } else { 432 | e.mr.role = PUBLISHER 433 | pubmap[e.mr.app] = e.mr 434 | } 435 | case e.id == E_PLAY: 436 | e.mr.role = PLAYER 437 | e.mr.que = make(chan *Msg, 16) 438 | for _, mr := range idmap { 439 | if mr.role == PUBLISHER && mr.app == e.mr.app && mr.stat == WAIT_DATA { 440 | e.mr.W = mr.W 441 | e.mr.H = mr.H 442 | e.mr.extraA = mr.extraA 443 | e.mr.extraV = mr.extraV 444 | e.mr.meta = mr.meta 445 | } 446 | } 447 | case e.id == E_CLOSE: 448 | if e.mr.role == PUBLISHER { 449 | delete(pubmap, e.mr.app) 450 | for _, mr := range idmap { 451 | if mr.role == PLAYER && mr.app == e.mr.app { 452 | ch := reflect.ValueOf(mr.que) 453 | var m *Msg = nil 454 | ch.TrySend(reflect.ValueOf(m)) 455 | } 456 | } 457 | } 458 | delete(idmap, e.mr.id) 459 | case e.id == E_DATA && e.mr.stat == WAIT_EXTRA: 460 | if len(e.mr.extraA) == 0 && e.m.typeid == MSG_AUDIO { 461 | l.Printf("event %v: got aac config", e.mr) 462 | e.mr.extraA = e.m.data.Bytes() 463 | } 464 | if len(e.mr.extraV) == 0 && e.m.typeid == MSG_VIDEO { 465 | l.Printf("event %v: got pps", e.mr) 466 | e.mr.extraV = e.m.data.Bytes() 467 | } 468 | if len(e.mr.extraA) > 0 && len(e.mr.extraV) > 0 { 469 | l.Printf("event %v: got all extra", e.mr) 470 | e.mr.stat = WAIT_DATA 471 | for _, mr := range idmap { 472 | if mr.role == PLAYER && mr.app == e.mr.app { 473 | mr.W = e.mr.W 474 | mr.H = e.mr.H 475 | mr.extraA = e.mr.extraA 476 | mr.extraV = e.mr.extraV 477 | mr.meta = e.mr.meta 478 | } 479 | } 480 | } 481 | case e.id == E_DATA && e.mr.stat == WAIT_DATA: 482 | for _, mr := range idmap { 483 | if mr.role == PLAYER && mr.app == e.mr.app { 484 | ch := reflect.ValueOf(mr.que) 485 | ok := ch.TrySend(reflect.ValueOf(e.m)) 486 | if !ok { 487 | l.Printf("event %v: send failed", e.mr) 488 | } else { 489 | l.Printf("event %v: send ok", e.mr) 490 | } 491 | } 492 | } 493 | } 494 | eventDone <- 1 495 | } 496 | } 497 | 498 | func SimpleServer() { 499 | l.Printf("server: simple server starts") 500 | ln, err := net.Listen("tcp", ":1935") 501 | if err != nil { 502 | l.Printf("server: error: listen 1935 %s\n", err) 503 | return 504 | } 505 | go listenEvent() 506 | for { 507 | c, err := ln.Accept() 508 | if err != nil { 509 | l.Printf("server: error: sock accept %s\n", err) 510 | break 511 | } 512 | go func (c net.Conn) { 513 | mr := NewMsgStream(c) 514 | event <- eventS{id:E_NEW, mr:mr} 515 | <-eventDone 516 | serve(mr) 517 | } (c) 518 | } 519 | } 520 | 521 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | 2 | package rtmp 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | type logger int 13 | 14 | func (l logger) Printf(format string, v ...interface{}) { 15 | str := fmt.Sprintf(format, v...) 16 | switch { 17 | case strings.HasPrefix(str, "server") && l >= 1, 18 | strings.HasPrefix(str, "stream") && l >= 1, 19 | strings.HasPrefix(str, "event") && l >= 1, 20 | strings.HasPrefix(str, "data") && l >= 1, 21 | strings.HasPrefix(str, "msg") && l >= 2: 22 | l2.Println(str) 23 | default: 24 | if l >= 1 { 25 | l2.Println(str) 26 | } 27 | } 28 | } 29 | 30 | var ( 31 | l = logger(0) 32 | l2 *log.Logger 33 | ) 34 | 35 | func init() { 36 | l2 = log.New(os.Stderr, "", 0) 37 | l2.SetFlags(log.Lmicroseconds) 38 | } 39 | 40 | func LogLevel(i int) { 41 | l = logger(i) 42 | } 43 | 44 | type stream struct { 45 | r io.ReadWriteCloser 46 | } 47 | 48 | func (s stream) Read(p []byte) (n int, err error) { 49 | n, err = s.r.Read(p) 50 | if err != nil { 51 | panic(err) 52 | } 53 | return 54 | } 55 | 56 | func (s stream) Write(p []byte) (n int, err error) { 57 | n, err = s.r.Write(p) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return 62 | } 63 | 64 | func (s stream) Close() { 65 | s.r.Close() 66 | } 67 | 68 | func ReadBuf(r io.Reader, n int) (b []byte) { 69 | b = make([]byte, n) 70 | r.Read(b) 71 | return 72 | } 73 | 74 | func ReadInt(r io.Reader, n int) (ret int) { 75 | b := ReadBuf(r, n) 76 | for i := 0; i < n; i++ { 77 | ret <<= 8 78 | ret += int(b[i]) 79 | } 80 | return 81 | } 82 | 83 | func ReadIntLE(r io.Reader, n int) (ret int) { 84 | b := ReadBuf(r, n) 85 | for i := 0; i < n; i++ { 86 | ret <<= 8 87 | ret += int(b[n-i-1]) 88 | } 89 | return 90 | } 91 | 92 | func WriteBuf(w io.Writer, buf []byte) { 93 | w.Write(buf) 94 | } 95 | 96 | func WriteInt(w io.Writer, v int, n int) { 97 | b := make([]byte, n) 98 | for i := 0; i < n; i++ { 99 | b[n-i-1] = byte(v&0xff) 100 | v >>= 8 101 | } 102 | WriteBuf(w, b) 103 | } 104 | 105 | func WriteIntLE(w io.Writer, v int, n int) { 106 | b := make([]byte, n) 107 | for i := 0; i < n; i++ { 108 | b[i] = byte(v&0xff) 109 | v >>= 8 110 | } 111 | WriteBuf(w, b) 112 | } 113 | 114 | --------------------------------------------------------------------------------