From fc72f0734a7a3691e4da045f27ff5e2045675a66 Mon Sep 17 00:00:00 2001 From: saxon Date: Mon, 7 Jan 2019 10:30:13 +1030 Subject: [PATCH 01/34] mts/psi: remove read funcs as we're not using them at this time --- stream/mts/psi/psi.go | 103 ------------------------------------------ 1 file changed, 103 deletions(-) diff --git a/stream/mts/psi/psi.go b/stream/mts/psi/psi.go index 56abc260..9d9763ce 100644 --- a/stream/mts/psi/psi.go +++ b/stream/mts/psi/psi.go @@ -118,109 +118,6 @@ type Desc struct { Dd []byte // Descriptor data } -// ReadPSI creates a PSI data structure from a given byte slice that represents a PSI -func ReadPSI(data []byte) *PSI { - psi := PSI{} - pos := 0 - psi.Pf = data[pos] - if psi.Pf != 0 { - panic("No support for pointer filler bytes") - } - psi.Tid = data[pos] - pos++ - psi.Ssi = byteToBool(data[pos] & 0x80) - psi.Pb = byteToBool(data[pos] & 0x40) - psi.Sl = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) - pos += 2 - psi.Tss = readTSS(data[pos:], &psi) - return &psi -} - -// ReadTSS creates a TSS data structure from a given byte slice that represents a TSS -func readTSS(data []byte, p *PSI) *TSS { - tss := TSS{} - pos := 0 - tss.Tide = uint16(data[pos])<<8 | uint16(data[pos+1]) - pos += 2 - tss.V = (data[pos] & 0x3e) >> 1 - tss.Cni = byteToBool(data[pos] & 0x01) - pos++ - tss.Sn = data[pos] - pos++ - tss.Lsn = data[pos] - pos++ - switch p.Tid { - case PATTableID: - tss.Sd = readPAT(data[pos:], &tss) - case PMTTableID: - tss.Sd = readPMT(data[pos:], &tss) - default: - panic("Can't yet deal with tables that are not PAT or PMT") - } - return &tss -} - -// readPAT creates a pat struct based on a bytes slice representing a pat -func readPAT(data []byte, p *TSS) *PAT { - pat := PAT{} - pos := 0 - pat.Pn = uint16(data[pos])<<8 | uint16(data[pos+1]) - pos += 2 - pat.Pmpid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) - return &pat -} - -// readPMT creates a pmt struct based on a bytes slice that represents a pmt -func readPMT(data []byte, p *TSS) *PAT { - pmt := PMT{} - pos := 0 - pmt.Pcrpid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) - pos += 2 - pmt.Pil = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) - pos += 2 - if pmt.Pil != 0 { - pmt.Pd = readDescs(data[pos:], int(pmt.Pil)) - } - pos += int(pmt.Pil) - // TODO Read ES stuff - pmt.Essd = readEssd(data[pos:]) - return nil -} - -// readDescs reads provides a slice of Descs given a byte slice that represents Descs -// and the no of bytes that the descs accumilate -func readDescs(data []byte, descLen int) (o []Desc) { - pos := 0 - o = make([]Desc, 1) - o[0].Dt = data[pos] - pos++ - o[0].Dl = data[pos] - pos++ - o[0].Dd = make([]byte, o[0].Dl) - for i := 0; i < int(o[0].Dl); i++ { - o[0].Dd[i] = data[pos] - pos++ - } - if 2+len(o[0].Dd) != descLen { - panic("No support for reading more than one descriptor") - } - return -} - -// readEESD creates an ESSD struct based on a bytes slice that represents ESSD -func readEssd(data []byte) *ESSD { - essd := ESSD{} - pos := 0 - essd.St = data[pos] - pos++ - essd.Epid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) - pos += 2 - essd.Esil = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) - pos += 2 - essd.Esd = readDescs(data[pos:], int(essd.Esil)) - return &essd -} - // Bytes outputs a byte slice representation of the PSI func (p *PSI) Bytes() []byte { out := make([]byte, 4) From cbe6149c8fa4d9a2618fda5aa2a535d43a0f58eb Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 7 Jan 2019 23:59:41 +1030 Subject: [PATCH 02/34] Added proper logging. --- revid/senders.go | 4 +- rtmp/packet.go | 51 ++++++++------------ rtmp/rtmp.go | 121 ++++++++++++++++++----------------------------- rtmp/session.go | 26 ++++++---- 4 files changed, 86 insertions(+), 116 deletions(-) diff --git a/revid/senders.go b/revid/senders.go index 9c9abca1..f09d34fa 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -264,7 +264,7 @@ func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg var sess *rtmp.Session var err error for n := 0; n < retries; n++ { - sess = rtmp.NewSession(url, timeout) + sess = rtmp.NewSession(url, timeout, log) err = sess.Open() if err == nil { break @@ -310,7 +310,7 @@ func (s *rtmpSender) restart() error { return err } for n := 0; n < s.retries; n++ { - s.sess = rtmp.NewSession(s.url, s.timeout) + s.sess = rtmp.NewSession(s.url, s.timeout, s.log) err = s.sess.Open() if err == nil { break diff --git a/rtmp/packet.go b/rtmp/packet.go index 95efb658..d97d99ea 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -36,7 +36,6 @@ package rtmp import ( "encoding/binary" - "log" ) const ( @@ -101,7 +100,7 @@ func readPacket(s *Session, pkt *packet) error { err := readN(s, header[:1]) if err != nil { - log.Println("readPacket: failed to read RTMP packet header!") + s.log(DebugLevel, pkg+"failed to read packet header 1st byte", "error", err.Error()) return err } pkt.headerType = (header[0] & 0xc0) >> 6 @@ -112,7 +111,7 @@ func readPacket(s *Session, pkt *packet) error { case pkt.channel == 0: err = readN(s, header[:1]) if err != nil { - log.Println("readPacket: failed to read rtmp packet header 2nd byte.") + s.log(DebugLevel, pkg+"failed to read packet header 2nd byte", "error", err.Error()) return err } header = header[1:] @@ -121,12 +120,11 @@ func readPacket(s *Session, pkt *packet) error { case pkt.channel == 1: err = readN(s, header[:2]) if err != nil { - log.Println("readPacket: failed to read RTMP packet 3rd byte") + s.log(DebugLevel, pkg+"failed to read packet header 3rd byte", "error", err.Error()) return err } header = header[2:] pkt.channel = int32(binary.BigEndian.Uint16(header[:2])) + 64 - } if pkt.channel >= s.channelsAllocatedIn { @@ -161,22 +159,19 @@ func readPacket(s *Session, pkt *packet) error { *pkt = *(s.vecChannelsIn[pkt.channel]) } } - size-- if size > 0 { err = readN(s, header[:size]) if err != nil { - log.Println("readPacket: failed to read rtmp packet heades.") + s.log(DebugLevel, pkg+"failed to read packet header", "error", err.Error()) return err } } - hSize := len(hbuf) - len(header) + size if size >= 3 { pkt.timestamp = C_AMF_DecodeInt24(header[:3]) - if size >= 6 { pkt.bodySize = C_AMF_DecodeInt24(header[3:6]) pkt.bytesRead = 0 @@ -195,7 +190,7 @@ func readPacket(s *Session, pkt *packet) error { if extendedTimestamp { err = readN(s, header[size:size+4]) if err != nil { - log.Println("readPacket: Failed to read extended timestamp") + s.log(DebugLevel, pkg+"failed to read extended timestamp", "error", err.Error()) return err } // TODO: port this @@ -222,7 +217,7 @@ func readPacket(s *Session, pkt *packet) error { err = readN(s, pkt.body[pkt.bytesRead:][:chunkSize]) if err != nil { - log.Println("readPacket: failed to read RTMP packet body") + s.log(DebugLevel, pkg+"failed to read packet body", "error", err.Error()) return err } @@ -254,7 +249,7 @@ func readPacket(s *Session, pkt *packet) error { return nil } -// resizePacket adjust the packet's storage to accommodate a body of the given size. +// resizePacket adjusts the packet's storage to accommodate a body of the given size. func resizePacket(pkt *packet, size uint32, ht uint8) { buf := make([]byte, RTMP_MAX_HEADER_SIZE+size) pkt.headerType = ht @@ -300,8 +295,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { } if pkt.headerType > 3 { - log.Printf("Sanity failed! trying to send header of type: 0x%02x.", - pkt.headerType) + s.log(WarnLevel, pkg+"unexpected header type", "type", pkt.headerType) return errInvalidHeader } @@ -339,7 +333,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if ts >= 0xffffff { origIdx -= 4 hSize += 4 - log.Printf("Larger timestamp than 24-bit: 0x%v", ts) + s.log(DebugLevel, pkg+"larger timestamp than 24 bits", "timestamp", ts) } headerIdx := origIdx @@ -396,39 +390,37 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { size := int(pkt.bodySize) chunkSize := int(s.outChunkSize) - if debugMode { - log.Printf("sendPacket: %v->%v, size=%v", s.link.conn.LocalAddr(), s.link.conn.RemoteAddr(), size) - } + s.log(DebugLevel, pkg+"sending packet", "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr(), "size", size) - // Send the previously deferred packet if combining it with the next packet would exceed the chunk size. - if s.defered != nil && len(s.defered)+size+hSize > chunkSize { - err := writeN(s, s.defered) + if s.deferred != nil && len(s.deferred)+size+hSize > chunkSize { + err := writeN(s, s.deferred) if err != nil { return err } - s.defered = nil + s.deferred = nil } // TODO(kortschak): Rewrite this horrific peice of premature optimisation. // NB: RTMP wants packets in chunks which are 128 bytes by default, but the server may request a different size. for size+hSize != 0 { - if s.defered == nil && pkt.packetType == RTMP_PACKET_TYPE_AUDIO && size < chunkSize { - s.defered = headBytes[origIdx:][:size+hSize] + if s.deferred == nil && pkt.packetType == RTMP_PACKET_TYPE_AUDIO && size < chunkSize { + s.deferred = headBytes[origIdx:][:size+hSize] + s.log(DebugLevel, pkg+"deferred sending packet") break } if chunkSize > size { chunkSize = size } bytes := headBytes[origIdx:][:chunkSize+hSize] - if s.defered != nil { + if s.deferred != nil { // Prepend the previously deferred packet and write it with the current one. - bytes = append(s.defered, bytes...) + bytes = append(s.deferred, bytes...) } err := writeN(s, bytes) if err != nil { return err } - s.defered = nil + s.deferred = nil size -= chunkSize origIdx += chunkSize + hSize @@ -464,10 +456,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if pkt.packetType == RTMP_PACKET_TYPE_INVOKE { buf := pkt.body[1:] meth := C_AMF_DecodeString(buf) - - if debugMode { - log.Printf("invoking %v", meth) - } + s.log(DebugLevel, "invoking method", "method", meth) // keep it in call queue till result arrives if queue { buf = buf[3+len(meth):] diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index d73c2fc2..2574a785 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -38,9 +38,7 @@ import ( "bytes" "encoding/binary" "errors" - "fmt" "io" - "log" "math/rand" "net" "strconv" @@ -48,9 +46,8 @@ import ( ) const ( + pkg = "rtmp:" minDataSize = 11 - debugMode = false - length = 512 ) const ( @@ -123,6 +120,7 @@ var rtmpProtocolStrings = [...]string{ // RTMP errors. var ( errUnknownScheme = errors.New("rtmp: unknown scheme") + errConnected = errors.New("rtmp: already connected") errNotConnected = errors.New("rtmp: not connected") errHandshake = errors.New("rtmp: handshake failed") errConnSend = errors.New("rtmp: connection send error") @@ -144,8 +142,7 @@ func setupURL(s *Session, addr string) (err error) { if s.link.tcUrl == "" { if s.link.app != "" { - s.link.tcUrl = fmt.Sprintf("%v://%v:%v/%v", - rtmpProtocolStrings[s.link.protocol], s.link.host, s.link.port, s.link.app) + s.link.tcUrl = rtmpProtocolStrings[s.link.protocol] + "://" + s.link.host + ":" + strconv.Itoa(int(s.link.port)) + "/" + s.link.app s.link.lFlags |= RTMP_LF_FTCU } else { s.link.tcUrl = addr @@ -156,6 +153,7 @@ func setupURL(s *Session, addr string) (err error) { switch { case (s.link.protocol & RTMP_FEATURE_SSL) != 0: s.link.port = 433 + s.log(FatalLevel, pkg+"SSL not supported") case (s.link.protocol & RTMP_FEATURE_HTTP) != 0: s.link.port = 80 default: @@ -173,22 +171,19 @@ func connect(s *Session) error { } s.link.conn, err = net.DialTCP("tcp4", nil, addr) if err != nil { + s.log(WarnLevel, pkg+"dial failed", "error", err.Error()) return err } - if debugMode { - log.Println("... connected, handshaking...") - } + s.log(DebugLevel, pkg+"connected") err = handshake(s) if err != nil { - log.Println("connect: handshake failed") + s.log(WarnLevel, pkg+"handshake failed", "error", err.Error()) return errHandshake } - if debugMode { - log.Println("... handshaked...") - } + s.log(DebugLevel, pkg+"handshaked") err = sendConnectPacket(s) if err != nil { - log.Println("connect: sendConnect failed") + s.log(WarnLevel, pkg+"sendConnect failed", "error", err.Error()) return errConnSend } return nil @@ -211,7 +206,7 @@ func connectStream(s *Session) error { if pkt.packetType == RTMP_PACKET_TYPE_AUDIO || pkt.packetType == RTMP_PACKET_TYPE_VIDEO || pkt.packetType == RTMP_PACKET_TYPE_INFO { - log.Println("connectStream: got packet before play()! Ignoring.") + s.log(DebugLevel, pkg+"got packet before play; ignoring") pkt.body = nil continue } @@ -241,7 +236,7 @@ func handlePacket(s *Session, pkt *packet) int32 { s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) case RTMP_PACKET_TYPE_CONTROL: - panic("Unsupported packet type RTMP_PACKET_TYPE_CONTROL") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_CONTROL") case RTMP_PACKET_TYPE_SERVER_BW: s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) @@ -255,34 +250,30 @@ func handlePacket(s *Session, pkt *packet) int32 { } case RTMP_PACKET_TYPE_AUDIO: - panic("Unsupported packet type RTMP_PACKET_TYPE_AUDIO") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_AUDIO") case RTMP_PACKET_TYPE_VIDEO: - panic("Unsupported packet type RTMP_PACKET_TYPE_VIDEO") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_VIDEO") case RTMP_PACKET_TYPE_FLEX_MESSAGE: - panic("Unsupported packet type RTMP_PACKET_TYPE_FLEX_MESSAGE") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_FLEX_MESSAGE") case RTMP_PACKET_TYPE_INFO: - panic("Unsupported packet type RTMP_PACKET_TYPE_INFO") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_INFO") case RTMP_PACKET_TYPE_INVOKE: - if debugMode { - log.Println("RTMP_PACKET_TYPE_INVOKE:") - } err := handleInvoke(s, pkt.body[:pkt.bodySize]) if err != nil { // This will never happen with the methods we implement. - log.Println("HasMediaPacket") + s.log(WarnLevel, pkg+"unexpected error from handleInvoke", "error", err.Error()) hasMediaPacket = 2 } case RTMP_PACKET_TYPE_FLASH_VIDEO: - panic("Unsupported packet type RTMP_PACKET_TYPE_FLASH_VIDEO") + s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_FLASH_VIDEO") default: - // TODO use new logger here - // RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__,pkt.packetType); + s.log(WarnLevel, pkg+"unknown packet type", "type", pkt.packetType) } return hasMediaPacket } @@ -294,9 +285,7 @@ func readN(s *Session, buf []byte) error { } n, err := io.ReadFull(s.link.conn, buf) if err != nil { - if debugMode { - log.Printf("readN error: %v\n", err) - } + s.log(WarnLevel, pkg+"read failed", "error", err.Error()) s.close() return err } @@ -318,9 +307,7 @@ func writeN(s *Session, buf []byte) error { } _, err = s.link.conn.Write(buf) if err != nil { - if debugMode { - log.Printf("writeN, RTMP send error: %v\n", err) - } + s.log(WarnLevel, pkg+"write failed", "error", err.Error()) s.close() return err } @@ -706,9 +693,8 @@ func handleInvoke(s *Session, body []byte) error { } meth := C_AMFProp_GetString(C_AMF_GetProp(&obj, "", 0)) + s.log(DebugLevel, pkg+"invoking", "method", meth) txn := C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 1)) - // TODO use new logger here - // RTMP_Log(RTMP_LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val); switch meth { case av__result: @@ -721,30 +707,27 @@ func handleInvoke(s *Session, body []byte) error { } } if methodInvoked == "" { - // TODO use new logger here - //RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %f without matching request", - //__FUNCTION__, txn); + s.log(WarnLevel, pkg+"received result without matching request", "id", txn) goto leave } - // TODO use new logger here - //RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, - //methodInvoked.av_val); + s.log(DebugLevel, pkg+"received result for method", "id", txn) + switch methodInvoked { case av_connect: if s.link.token != "" { - panic("No support for link token") + s.log(FatalLevel, "no support for link token") } if (s.link.protocol & RTMP_FEATURE_WRITE) != 0 { sendReleaseStream(s) sendFCPublish(s) } else { - panic("Link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") } sendCreateStream(s) if (s.link.protocol & RTMP_FEATURE_WRITE) == 0 { - panic("Link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") } case av_createStream: @@ -753,13 +736,12 @@ func handleInvoke(s *Session, body []byte) error { if s.link.protocol&RTMP_FEATURE_WRITE != 0 { sendPublish(s) } else { - panic("Link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") } case av_play, av_publish: - panic("Unsupported method av_play/av_publish") + s.log(FatalLevel, "unsupported method av_play/av_publish") } - //C.free(unsafe.Pointer(methodInvoked.av_val)) case av_onBWDone: if s.bwCheckCounter == 0 { @@ -767,38 +749,34 @@ func handleInvoke(s *Session, body []byte) error { } case av_onFCUnsubscribe, av_onFCSubscribe: - panic("Unsupported method av_onFCUnsubscribe/av_onFCSubscribe") + s.log(FatalLevel, "unsupported method av_onFCUnsubscribe/av_onFCSubscribe") case av_ping: - panic("Unsupported method av_ping") + s.log(FatalLevel, "unsupported method av_ping") case av__onbwcheck: - panic("Unsupported method av_onbwcheck") + s.log(FatalLevel, "unsupported method av_onbwcheck") case av__onbwdone: - panic("Unsupported method av_onbwdone") + s.log(FatalLevel, "unsupported method av_onbwdone") case av_close: - panic("Unsupported method av_close") + s.log(FatalLevel, "unsupported method av_close") case av_onStatus: var obj2 C_AMFObject C_AMFProp_GetObject(C_AMF_GetProp(&obj, "", 3), &obj2) code := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_code, -1)) - level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_level, -1)) // Not used. - _ = level - - // TODO use new logger - // RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val); + s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { case av_NetStream_Failed, av_NetStream_Play_Failed, av_NetStream_Play_StreamNotFound, av_NetConnection_Connect_InvalidApp: - panic("Unsupported method av_NetStream/av_NetStream_Play_Failed/av_netSTream_Play_StreamNotFound/av_netConnection_Connect_invalidApp") + s.log(FatalLevel, "unsupported method av_NetStream/av_NetStream_Play_Failed/av_netSTream_Play_StreamNotFound/av_netConnection_Connect_invalidApp") case av_NetStream_Play_Start, av_NetStream_Play_PublishNotify: - panic("Unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") + s.log(FatalLevel, "unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") case av_NetStream_Publish_Start: s.isPlaying = true @@ -810,20 +788,20 @@ func handleInvoke(s *Session, body []byte) error { } case av_NetStream_Play_Complete, av_NetStream_Play_Stop, av_NetStream_Play_UnpublishNotify: - panic("Unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") + s.log(FatalLevel, "unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") case av_NetStream_Seek_Notify: - panic("Unsupported method av_netStream_Seek_Notify") + s.log(FatalLevel, "unsupported method av_netStream_Seek_Notify") case av_NetStream_Pause_Notify: - panic("Unsupported method av_NetStream_Pause_Notify") + s.log(FatalLevel, "unsupported method av_NetStream_Pause_Notify") } case av_playlist_ready: - panic("Unsupported method av_playlist_ready") + s.log(FatalLevel, "unsupported method av_playlist_ready") default: - panic(fmt.Sprintf("unknown method: %q", meth)) + s.log(FatalLevel, "unknown method", "method", meth) } leave: C_AMF_Reset(&obj) @@ -854,12 +832,9 @@ func handshake(s *Session) error { return err } - if debugMode { - log.Printf("handshake: Type answer: %v\n", typ[0]) - } + s.log(DebugLevel, pkg+"handshake", "received", typ[0]) if typ[0] != clientbuf[0] { - log.Printf("handshake: type mismatch: client sent %v, server sent: %v\n", - clientbuf[0], typ) + s.log(WarnLevel, pkg+"handshake type mismatch", "sent", clientbuf[0], "received", typ) } err = readN(s, serversig[:]) if err != nil { @@ -868,10 +843,7 @@ func handshake(s *Session) error { // decode server response suptime := binary.BigEndian.Uint32(serversig[:4]) - _ = suptime - // RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime) - // RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__, - // serversig[4], serversig[5], serversig[6], serversig[7]) + s.log(DebugLevel, pkg+"server uptime", "uptime", suptime) // 2nd part of handshake err = writeN(s, serversig[:]) @@ -885,8 +857,7 @@ func handshake(s *Session) error { } if !bytes.Equal(serversig[:RTMP_SIG_SIZE], clientbuf[1:RTMP_SIG_SIZE+1]) { - log.Printf("Client signature does not match: %q != %q", - serversig[:RTMP_SIG_SIZE], clientbuf[1:RTMP_SIG_SIZE+1]) + s.log(WarnLevel, pkg+"signature mismatch", "serversig", serversig[:RTMP_SIG_SIZE], "clientsig", clientbuf[1:RTMP_SIG_SIZE+1]) } return nil } diff --git a/rtmp/session.go b/rtmp/session.go index 6d1fad40..a04f39d5 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -33,10 +33,6 @@ LICENSE */ package rtmp -import ( - "errors" -) - // Session holds the state for an RTMP session. type Session struct { url string @@ -62,22 +58,36 @@ type Session struct { audioCodecs float64 videoCodecs float64 encoding float64 - defered []byte + deferred []byte link link + log Log } +// Log defines the RTMP logging function. +type Log func(level int8, message string, params ...interface{}) + +// Log levels used by Log. +const ( + DebugLevel int8 = -1 + InfoLevel int8 = 0 + WarnLevel int8 = 1 + ErrorLevel int8 = 2 + FatalLevel int8 = 5 +) + // NewSession returns a new Session. -func NewSession(url string, connectTimeout uint) *Session { +func NewSession(url string, timeout uint, log Log) *Session { return &Session{ url: url, - timeout: connectTimeout, + timeout: timeout, + log: log, } } // Open establishes an rtmp connection with the url passed into the constructor. func (s *Session) Open() error { if s.isConnected() { - return errors.New("rtmp: attempt to start already running session") + return errConnected } err := s.start() if err != nil { From 88e1415b10eaed1f649a1df0dde98fe3d381e331 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 16:08:02 +1030 Subject: [PATCH 03/34] Include pkg name in fatal log messages. --- rtmp/rtmp.go | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 2574a785..35084bb0 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -236,7 +236,7 @@ func handlePacket(s *Session, pkt *packet) int32 { s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) case RTMP_PACKET_TYPE_CONTROL: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_CONTROL") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_CONTROL") case RTMP_PACKET_TYPE_SERVER_BW: s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) @@ -250,16 +250,16 @@ func handlePacket(s *Session, pkt *packet) int32 { } case RTMP_PACKET_TYPE_AUDIO: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_AUDIO") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_AUDIO") case RTMP_PACKET_TYPE_VIDEO: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_VIDEO") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_VIDEO") case RTMP_PACKET_TYPE_FLEX_MESSAGE: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_FLEX_MESSAGE") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_FLEX_MESSAGE") case RTMP_PACKET_TYPE_INFO: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_INFO") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_INFO") case RTMP_PACKET_TYPE_INVOKE: err := handleInvoke(s, pkt.body[:pkt.bodySize]) @@ -270,7 +270,7 @@ func handlePacket(s *Session, pkt *packet) int32 { } case RTMP_PACKET_TYPE_FLASH_VIDEO: - s.log(FatalLevel, "unsupported packet type RTMP_PACKET_TYPE_FLASH_VIDEO") + s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_FLASH_VIDEO") default: s.log(WarnLevel, pkg+"unknown packet type", "type", pkt.packetType) @@ -693,7 +693,7 @@ func handleInvoke(s *Session, body []byte) error { } meth := C_AMFProp_GetString(C_AMF_GetProp(&obj, "", 0)) - s.log(DebugLevel, pkg+"invoking", "method", meth) + s.log(DebugLevel, pkg+"invoking method "+meth) txn := C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 1)) switch meth { @@ -715,19 +715,19 @@ func handleInvoke(s *Session, body []byte) error { switch methodInvoked { case av_connect: if s.link.token != "" { - s.log(FatalLevel, "no support for link token") + s.log(FatalLevel, pkg+"no support for link token") } if (s.link.protocol & RTMP_FEATURE_WRITE) != 0 { sendReleaseStream(s) sendFCPublish(s) } else { - s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") } sendCreateStream(s) if (s.link.protocol & RTMP_FEATURE_WRITE) == 0 { - s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") } case av_createStream: @@ -736,11 +736,11 @@ func handleInvoke(s *Session, body []byte) error { if s.link.protocol&RTMP_FEATURE_WRITE != 0 { sendPublish(s) } else { - s.log(FatalLevel, "link protocol has no RTMP_FEATURE_WRITE") + s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") } case av_play, av_publish: - s.log(FatalLevel, "unsupported method av_play/av_publish") + s.log(FatalLevel, pkg+"unsupported method av_play/av_publish") } case av_onBWDone: @@ -749,19 +749,19 @@ func handleInvoke(s *Session, body []byte) error { } case av_onFCUnsubscribe, av_onFCSubscribe: - s.log(FatalLevel, "unsupported method av_onFCUnsubscribe/av_onFCSubscribe") + s.log(FatalLevel, pkg+"unsupported method av_onFCUnsubscribe/av_onFCSubscribe") case av_ping: - s.log(FatalLevel, "unsupported method av_ping") + s.log(FatalLevel, pkg+"unsupported method av_ping") case av__onbwcheck: - s.log(FatalLevel, "unsupported method av_onbwcheck") + s.log(FatalLevel, pkg+"unsupported method av_onbwcheck") case av__onbwdone: - s.log(FatalLevel, "unsupported method av_onbwdone") + s.log(FatalLevel, pkg+"unsupported method av_onbwdone") case av_close: - s.log(FatalLevel, "unsupported method av_close") + s.log(FatalLevel, pkg+"unsupported method av_close") case av_onStatus: var obj2 C_AMFObject @@ -773,10 +773,10 @@ func handleInvoke(s *Session, body []byte) error { switch code { case av_NetStream_Failed, av_NetStream_Play_Failed, av_NetStream_Play_StreamNotFound, av_NetConnection_Connect_InvalidApp: - s.log(FatalLevel, "unsupported method av_NetStream/av_NetStream_Play_Failed/av_netSTream_Play_StreamNotFound/av_netConnection_Connect_invalidApp") + s.log(FatalLevel, pkg+"unsupported method av_NetStream/av_NetStream_Play_Failed/av_netSTream_Play_StreamNotFound/av_netConnection_Connect_invalidApp") case av_NetStream_Play_Start, av_NetStream_Play_PublishNotify: - s.log(FatalLevel, "unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") + s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") case av_NetStream_Publish_Start: s.isPlaying = true @@ -788,20 +788,20 @@ func handleInvoke(s *Session, body []byte) error { } case av_NetStream_Play_Complete, av_NetStream_Play_Stop, av_NetStream_Play_UnpublishNotify: - s.log(FatalLevel, "unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") + s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") case av_NetStream_Seek_Notify: - s.log(FatalLevel, "unsupported method av_netStream_Seek_Notify") + s.log(FatalLevel, pkg+"unsupported method av_netStream_Seek_Notify") case av_NetStream_Pause_Notify: - s.log(FatalLevel, "unsupported method av_NetStream_Pause_Notify") + s.log(FatalLevel, pkg+"unsupported method av_NetStream_Pause_Notify") } case av_playlist_ready: - s.log(FatalLevel, "unsupported method av_playlist_ready") + s.log(FatalLevel, pkg+"unsupported method av_playlist_ready") default: - s.log(FatalLevel, "unknown method", "method", meth) + s.log(FatalLevel, pkg+"unknown method "+meth) } leave: C_AMF_Reset(&obj) From 0a69c59f5059f30730e056f19734edd487fda598 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 17:33:19 +1030 Subject: [PATCH 04/34] Additional logging. --- rtmp/packet.go | 4 ++-- rtmp/rtmp.go | 4 ++-- rtmp/session.go | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index d97d99ea..9589dc6b 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -390,7 +390,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { size := int(pkt.bodySize) chunkSize := int(s.outChunkSize) - s.log(DebugLevel, pkg+"sending packet", "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr(), "size", size) + s.log(DebugLevel, pkg+"sending packet", "size", size, "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr()) if s.deferred != nil && len(s.deferred)+size+hSize > chunkSize { err := writeN(s, s.deferred) @@ -456,9 +456,9 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if pkt.packetType == RTMP_PACKET_TYPE_INVOKE { buf := pkt.body[1:] meth := C_AMF_DecodeString(buf) - s.log(DebugLevel, "invoking method", "method", meth) // keep it in call queue till result arrives if queue { + s.log(DebugLevel, pkg+"queuing method "+meth) buf = buf[3+len(meth):] txn := int32(C_AMF_DecodeNumber(buf[:8])) s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 35084bb0..c4a15108 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -285,7 +285,7 @@ func readN(s *Session, buf []byte) error { } n, err := io.ReadFull(s.link.conn, buf) if err != nil { - s.log(WarnLevel, pkg+"read failed", "error", err.Error()) + s.log(DebugLevel, pkg+"read failed", "error", err.Error()) s.close() return err } @@ -710,7 +710,7 @@ func handleInvoke(s *Session, body []byte) error { s.log(WarnLevel, pkg+"received result without matching request", "id", txn) goto leave } - s.log(DebugLevel, pkg+"received result for method", "id", txn) + s.log(DebugLevel, pkg+"received result for "+methodInvoked) switch methodInvoked { case av_connect: diff --git a/rtmp/session.go b/rtmp/session.go index a04f39d5..fe548e75 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -98,6 +98,7 @@ func (s *Session) Open() error { // start does the heavylifting for Open(). func (s *Session) start() error { + s.log(DebugLevel, pkg+"Session.start") s.init() err := setupURL(s, s.url) if err != nil { @@ -146,6 +147,7 @@ func (s *Session) Close() error { // close does the heavylifting for Close(). // Any errors are ignored as it is often called in response to an earlier error. func (s *Session) close() { + s.log(DebugLevel, pkg+"Session.close") if s.isConnected() { if s.streamID > 0 { if s.link.protocol&RTMP_FEATURE_WRITE != 0 { From d1958ff75d73ca0ea56e5b1a1358c34025227bc4 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 17:34:38 +1030 Subject: [PATCH 05/34] Initial revision. --- rtmp/rtmp_test.go | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 rtmp/rtmp_test.go diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go new file mode 100644 index 00000000..ea5f687c --- /dev/null +++ b/rtmp/rtmp_test.go @@ -0,0 +1,163 @@ +/* +NAME + rtmp_test.go + +DESCRIPTION + RTMP tests + +AUTHORS + Alan Noble + +LICENSE + rtmp_test.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package rtmp + +import ( + "fmt" + "io/ioutil" + "os" + "runtime" + "testing" +) + +const ( + rtmpProtocol = "rtmp" + testHost = "a.rtmp.youtube.com" + testApp = "live2" + testBaseURL = rtmpProtocol + "://" + testHost + "/" + testApp + "/" + testTimeout = 30 +) + +// debug enables extra logging +var testDebug bool + +// testKey is the RTMP key required for YouTube streaming (RTMP_TEST_KEY env var) +var testKey string + +// testFile is the test video file (RTMP_TEST_FILE env var) +var testFile string + +// testLog is a bare bones logger that logs to stdout. +func testLog(level int8, msg string, params ...interface{}) { + logLevels := [...]string{"Debug", "Info", "Warn", "Error", "", "", "Fatal"} + if level < -1 || level > 5 { + panic("Invalid log level") + } + if testDebug && len(params) >= 2 { + switch params[0].(string) { + case "error": + fmt.Printf("%s: %s, error=%v\n", logLevels[level+1], msg, params[1].(string)) + case "size": + fmt.Printf("%s: %s, size=%d\n", logLevels[level+1], msg, params[1].(int)) + default: + fmt.Printf("%s: %s\n", logLevels[level+1], msg) + } + } else { + fmt.Printf("%s: %s\n", logLevels[level+1], msg) + } + if level == 5 { + // Fatal + buf := make([]byte, 1<<16) + size := runtime.Stack(buf, true) + fmt.Printf("%s\n", string(buf[:size])) + os.Exit(1) + } +} + +func TestKey(t *testing.T) { + testLog(0, "TestKey") + testKey := os.Getenv("RTMP_TEST_KEY") + if testKey == "" { + t.Errorf("RTMP_TEST_KEY environment variable not defined") + os.Exit(1) + } + testLog(0, "Testing against URL "+testBaseURL+testKey) +} + +func TestSetupURL(t *testing.T) { + testLog(0, "TestSetupURL") + // test with just the base URL + s := NewSession(testBaseURL, testTimeout, testLog) + if s.url != testBaseURL && s.link.timeout != testTimeout { + t.Errorf("NewSession failed") + } + err := setupURL(s, s.url) + if err != nil { + t.Errorf("setupURL(testBaseURL) failed with error: %v", err) + } + // test again with the full URL + s = NewSession(testBaseURL+testKey, testTimeout, testLog) + err = setupURL(s, s.url) + if err != nil { + t.Errorf("setupURL(testBaseURL+testKey) failed with error: %v", err) + } + // test the parts are as expected + if rtmpProtocolStrings[s.link.protocol] != rtmpProtocol { + t.Errorf("setupURL returned wrong protocol: %v", s.link.protocol) + } + if s.link.host != testHost { + t.Errorf("setupURL returned wrong host: %v", s.link.host) + } + if s.link.app != testApp { + t.Errorf("setupURL returned wrong app: %v", s.link.app) + } +} + +func TestOpenClose(t *testing.T) { + testLog(0, "TestOpenClose") + s := NewSession(testBaseURL+testKey, testTimeout, testLog) + err := s.Open() + if err != nil { + t.Errorf("Session.Open failed with error: %v", err) + return + } + err = s.Close() + if err != nil { + t.Errorf("Session.Close failed with error: %v", err) + } +} + +func TestFromFile(t *testing.T) { + testLog(0, "TestFromFile") + testFile := os.Getenv("RTMP_TEST_FILE") + if testKey == "" { + t.Errorf("RTMP_TEST_FILE environment variable not defined") + os.Exit(1) + } + s := NewSession(testBaseURL+testKey, testTimeout, testLog) + err := s.Open() + if err != nil { + t.Errorf("Session.Open failed with error: %v", err) + } + video, err := ioutil.ReadFile(testFile) + if err != nil { + t.Errorf("Cannot open video file: %v", testFile) + } + // ToDo: rate limit writing + n, err := s.Write(video) + if err != nil { + t.Errorf("Session.Write failed with error: %v", err) + } + if n != len(video) { + t.Errorf("Session.Write retuned wrong length") + } + err = s.Close() + if err != nil { + t.Errorf("Session.Close failed with error: %v", err) + } +} From c8ec31782367dd69f07b56c465d68675c62a67ce Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 22:05:04 +1030 Subject: [PATCH 06/34] Merge init into NewSession. --- rtmp/session.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/rtmp/session.go b/rtmp/session.go index fe548e75..bd2a9e6d 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -78,9 +78,19 @@ const ( // NewSession returns a new Session. func NewSession(url string, timeout uint, log Log) *Session { return &Session{ - url: url, - timeout: timeout, - log: log, + url: url, + inChunkSize: 128, + outChunkSize: 128, + clientBW: 2500000, + clientBW2: 2, + serverBW: 2500000, + audioCodecs: 3191.0, + videoCodecs: 252.0, + log: log, + link: link{ + timeout: timeout, + swfAge: 30, + }, } } @@ -99,7 +109,6 @@ func (s *Session) Open() error { // start does the heavylifting for Open(). func (s *Session) start() error { s.log(DebugLevel, pkg+"Session.start") - s.init() err := setupURL(s, s.url) if err != nil { s.close() @@ -121,20 +130,6 @@ func (s *Session) start() error { return nil } -// init initializes various RTMP defauls. -// ToDo: define consts for the magic numbers. -func (s *Session) init() { - s.inChunkSize = RTMP_DEFAULT_CHUNKSIZE - s.outChunkSize = RTMP_DEFAULT_CHUNKSIZE - s.clientBW = 2500000 - s.clientBW2 = 2 - s.serverBW = 2500000 - s.audioCodecs = 3191.0 - s.videoCodecs = 252.0 - s.link.timeout = s.timeout - s.link.swfAge = 30 -} - // Close terminates the rtmp connection, func (s *Session) Close() error { if !s.isConnected() { From c386f45bbd7e35466af9269ee8ef4bb4233dcc33 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 22:25:10 +1030 Subject: [PATCH 07/34] Removed out of date comment. --- rtmp/rtmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index c4a15108..125410f3 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -767,7 +767,7 @@ func handleInvoke(s *Session, body []byte) error { var obj2 C_AMFObject C_AMFProp_GetObject(C_AMF_GetProp(&obj, "", 3), &obj2) code := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_code, -1)) - level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_level, -1)) // Not used. + level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_level, -1)) s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { From 076a9c030a50ef5cb6fad2c615d7318e4398036f Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 9 Jan 2019 22:51:07 +1030 Subject: [PATCH 08/34] Better field names. --- rtmp/packet.go | 43 ++++++++++++++++++++++--------------------- rtmp/rtmp.go | 2 +- rtmp/session.go | 7 +++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 9589dc6b..8980cf5d 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -132,20 +132,20 @@ func readPacket(s *Session, pkt *packet) error { timestamp := append(s.channelTimestamp, make([]int32, 10)...) var pkts []*packet - if s.vecChannelsIn == nil { + if s.channelsIn == nil { pkts = make([]*packet, n) } else { - pkts = append(s.vecChannelsIn[:pkt.channel:pkt.channel], make([]*packet, 10)...) + pkts = append(s.channelsIn[:pkt.channel:pkt.channel], make([]*packet, 10)...) } s.channelTimestamp = timestamp - s.vecChannelsIn = pkts + s.channelsIn = pkts for i := int(s.channelsAllocatedIn); i < len(s.channelTimestamp); i++ { s.channelTimestamp[i] = 0 } for i := int(s.channelsAllocatedIn); i < int(n); i++ { - s.vecChannelsIn[i] = nil + s.channelsIn[i] = nil } s.channelsAllocatedIn = n } @@ -155,8 +155,8 @@ func readPacket(s *Session, pkt *packet) error { case size == RTMP_LARGE_HEADER_SIZE: pkt.hasAbsTimestamp = true case size < RTMP_LARGE_HEADER_SIZE: - if s.vecChannelsIn[pkt.channel] != nil { - *pkt = *(s.vecChannelsIn[pkt.channel]) + if s.channelsIn[pkt.channel] != nil { + *pkt = *(s.channelsIn[pkt.channel]) } } size-- @@ -224,13 +224,13 @@ func readPacket(s *Session, pkt *packet) error { pkt.bytesRead += uint32(chunkSize) // keep the packet as ref for other packets on this channel - if s.vecChannelsIn[pkt.channel] == nil { - s.vecChannelsIn[pkt.channel] = &packet{} + if s.channelsIn[pkt.channel] == nil { + s.channelsIn[pkt.channel] = &packet{} } - *(s.vecChannelsIn[pkt.channel]) = *pkt + *(s.channelsIn[pkt.channel]) = *pkt if extendedTimestamp { - s.vecChannelsIn[pkt.channel].timestamp = 0xffffff + s.channelsIn[pkt.channel].timestamp = 0xffffff } if pkt.bytesRead != pkt.bodySize { @@ -243,9 +243,9 @@ func readPacket(s *Session, pkt *packet) error { } s.channelTimestamp[pkt.channel] = int32(pkt.timestamp) - s.vecChannelsIn[pkt.channel].body = nil - s.vecChannelsIn[pkt.channel].bytesRead = 0 - s.vecChannelsIn[pkt.channel].hasAbsTimestamp = false + s.channelsIn[pkt.channel].body = nil + s.channelsIn[pkt.channel].bytesRead = 0 + s.channelsIn[pkt.channel].hasAbsTimestamp = false return nil } @@ -266,20 +266,20 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { n := int(pkt.channel + 10) var pkts []*packet - if s.vecChannelsOut == nil { + if s.channelsOut == nil { pkts = make([]*packet, n) } else { - pkts = append(s.vecChannelsOut[:pkt.channel:pkt.channel], make([]*packet, 10)...) + pkts = append(s.channelsOut[:pkt.channel:pkt.channel], make([]*packet, 10)...) } - s.vecChannelsOut = pkts + s.channelsOut = pkts for i := int(s.channelsAllocatedOut); i < n; i++ { - s.vecChannelsOut[i] = nil + s.channelsOut[i] = nil } s.channelsAllocatedOut = int32(n) } - prevPkt = s.vecChannelsOut[pkt.channel] + prevPkt = s.channelsOut[pkt.channel] if prevPkt != nil && pkt.headerType != RTMP_PACKET_SIZE_LARGE { // compress a bit by using the prev packet's attributes @@ -415,6 +415,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if s.deferred != nil { // Prepend the previously deferred packet and write it with the current one. bytes = append(s.deferred, bytes...) + s.deferred = nil } err := writeN(s, bytes) if err != nil { @@ -465,10 +466,10 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { } } - if s.vecChannelsOut[pkt.channel] == nil { - s.vecChannelsOut[pkt.channel] = &packet{} + if s.channelsOut[pkt.channel] == nil { + s.channelsOut[pkt.channel] = &packet{} } - *(s.vecChannelsOut[pkt.channel]) = *pkt + *(s.channelsOut[pkt.channel]) = *pkt return nil } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 125410f3..eb0d5621 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -744,7 +744,7 @@ func handleInvoke(s *Session, body []byte) error { } case av_onBWDone: - if s.bwCheckCounter == 0 { + if s.checkCounter == 0 { // ToDo: why is this always zero? sendCheckBW(s) } diff --git a/rtmp/session.go b/rtmp/session.go index bd2a9e6d..a402a320 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -36,10 +36,9 @@ package rtmp // Session holds the state for an RTMP session. type Session struct { url string - timeout uint inChunkSize int32 outChunkSize int32 - bwCheckCounter int32 + checkCounter int32 nBytesIn int32 nBytesInSent int32 streamID int32 @@ -52,8 +51,8 @@ type Session struct { methodCalls []method channelsAllocatedIn int32 channelsAllocatedOut int32 - vecChannelsIn []*packet - vecChannelsOut []*packet + channelsIn []*packet + channelsOut []*packet channelTimestamp []int32 audioCodecs float64 videoCodecs float64 From e772d37a1b383ff77a6207f4e95a96d42ab3c799 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 12:48:31 +1030 Subject: [PATCH 09/34] Warn about EOF errors. --- rtmp/packet.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 8980cf5d..79bd99fe 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -35,6 +35,7 @@ LICENSE package rtmp import ( + "io" "encoding/binary" ) @@ -101,6 +102,9 @@ func readPacket(s *Session, pkt *packet) error { err := readN(s, header[:1]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 1st byte", "error", err.Error()) + if err == io.EOF { + s.log(WarnLevel, pkg+"EOF error; connection likely terminated") + } return err } pkt.headerType = (header[0] & 0xc0) >> 6 @@ -415,7 +419,6 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if s.deferred != nil { // Prepend the previously deferred packet and write it with the current one. bytes = append(s.deferred, bytes...) - s.deferred = nil } err := writeN(s, bytes) if err != nil { From 3dbaa810fc6d483293ad3d006d20f9a8f7028b3e Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 12:49:52 +1030 Subject: [PATCH 10/34] Additional logging. --- rtmp/rtmp.go | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index eb0d5621..2c6300d5 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -191,40 +191,38 @@ func connect(s *Session) error { // connectStream reads a packet and handles it func connectStream(s *Session) error { - var pkt packet - + var err error for !s.isPlaying && s.isConnected() { - err := readPacket(s, &pkt) + pkt := packet{} + err = readPacket(s, &pkt) if err != nil { break } - if pkt.bodySize == 0 { continue } - if pkt.packetType == RTMP_PACKET_TYPE_AUDIO || pkt.packetType == RTMP_PACKET_TYPE_VIDEO || pkt.packetType == RTMP_PACKET_TYPE_INFO { s.log(DebugLevel, pkg+"got packet before play; ignoring") - pkt.body = nil continue } - handlePacket(s, &pkt) - pkt.body = nil + err = handlePacket(s, &pkt) + if err != nil { + break + } } if !s.isPlaying { - return errConnStream + return err } return nil } // handlePacket handles a packet that the client has received. // NB: cases have been commented out that are not currently used by AusOcean -func handlePacket(s *Session, pkt *packet) int32 { - var hasMediaPacket int32 +func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { case RTMP_PACKET_TYPE_CHUNK_SIZE: @@ -266,7 +264,7 @@ func handlePacket(s *Session, pkt *packet) int32 { if err != nil { // This will never happen with the methods we implement. s.log(WarnLevel, pkg+"unexpected error from handleInvoke", "error", err.Error()) - hasMediaPacket = 2 + return err } case RTMP_PACKET_TYPE_FLASH_VIDEO: @@ -275,7 +273,7 @@ func handlePacket(s *Session, pkt *packet) int32 { default: s.log(WarnLevel, pkg+"unknown packet type", "type", pkt.packetType) } - return hasMediaPacket + return nil } func readN(s *Session, buf []byte) error { @@ -286,7 +284,6 @@ func readN(s *Session, buf []byte) error { n, err := io.ReadFull(s.link.conn, buf) if err != nil { s.log(DebugLevel, pkg+"read failed", "error", err.Error()) - s.close() return err } s.nBytesIn += int32(n) @@ -308,7 +305,6 @@ func writeN(s *Session, buf []byte) error { _, err = s.link.conn.Write(buf) if err != nil { s.log(WarnLevel, pkg+"write failed", "error", err.Error()) - s.close() return err } return nil @@ -404,12 +400,10 @@ func sendConnectPacket(s *Session) error { } } - if copy(enc, []byte{0, 0, AMF_OBJECT_END}) != 3 { - return errCopying // TODO: is this even possible? - } + copy(enc, []byte{0, 0, AMF_OBJECT_END}) enc = enc[3:] - /* add auth string */ + // add auth string if s.link.auth != "" { enc = C_AMF_EncodeBoolean(enc, s.link.lFlags&RTMP_LF_AUTH != 0) if enc == nil { @@ -430,7 +424,7 @@ func sendConnectPacket(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) + return sendPacket(s, &pkt, true) // response expected } func sendCreateStream(s *Session) error { @@ -458,7 +452,7 @@ func sendCreateStream(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) + return sendPacket(s, &pkt, true) // response expected } func sendReleaseStream(s *Session) error { @@ -589,7 +583,7 @@ func sendPublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) + return sendPacket(s, &pkt, true) // response expected } func sendDeleteStream(s *Session, dStreamId float64) error { @@ -779,6 +773,7 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") case av_NetStream_Publish_Start: + s.log(DebugLevel, pkg+"playing") s.isPlaying = true for i, m := range s.methodCalls { if m.name == av_publish { @@ -786,6 +781,7 @@ func handleInvoke(s *Session, body []byte) error { break } } + // ToDo: handle case when av_publish method not found case av_NetStream_Play_Complete, av_NetStream_Play_Stop, av_NetStream_Play_UnpublishNotify: s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") @@ -825,14 +821,15 @@ func handshake(s *Session) error { if err != nil { return err } + s.log(DebugLevel, pkg+"handshake sent") var typ [1]byte err = readN(s, typ[:]) if err != nil { return err } + s.log(DebugLevel, pkg+"handshake received") - s.log(DebugLevel, pkg+"handshake", "received", typ[0]) if typ[0] != clientbuf[0] { s.log(WarnLevel, pkg+"handshake type mismatch", "sent", clientbuf[0], "received", typ) } From 97964650186f3c3863f176cbe9e44f963545bbde Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 12:51:18 +1030 Subject: [PATCH 11/34] Exit if RTMP_TEST_KEY not defined. --- rtmp/rtmp_test.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index ea5f687c..2dc6781d 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -29,10 +29,10 @@ package rtmp import ( "fmt" - "io/ioutil" "os" "runtime" "testing" + "io/ioutil" ) const ( @@ -81,9 +81,9 @@ func testLog(level int8, msg string, params ...interface{}) { func TestKey(t *testing.T) { testLog(0, "TestKey") - testKey := os.Getenv("RTMP_TEST_KEY") + testKey = os.Getenv("RTMP_TEST_KEY") if testKey == "" { - t.Errorf("RTMP_TEST_KEY environment variable not defined") + fmt.Printf("RTMP_TEST_KEY environment variable not defined\n") os.Exit(1) } testLog(0, "Testing against URL "+testBaseURL+testKey) @@ -118,25 +118,35 @@ func TestSetupURL(t *testing.T) { } } +func TestOpen(t *testing.T) { + testLog(0, "TestOpen") + s := NewSession(testBaseURL+testKey, testTimeout, testLog) + err := setupURL(s, s.url) + if err != nil { + t.Errorf("setupURL failed with error: %v", err) + } + s.enableWrite() + err = s.Open() + if err != nil { + t.Errorf("connect failed with error: %v", err) + } +} + func TestOpenClose(t *testing.T) { testLog(0, "TestOpenClose") s := NewSession(testBaseURL+testKey, testTimeout, testLog) err := s.Open() if err != nil { - t.Errorf("Session.Open failed with error: %v", err) + t.Errorf("Open failed with error: %v", err) return } - err = s.Close() - if err != nil { - t.Errorf("Session.Close failed with error: %v", err) - } } func TestFromFile(t *testing.T) { testLog(0, "TestFromFile") testFile := os.Getenv("RTMP_TEST_FILE") if testKey == "" { - t.Errorf("RTMP_TEST_FILE environment variable not defined") + fmt.Printf("RTMP_TEST_FILE environment variable not defined\n") os.Exit(1) } s := NewSession(testBaseURL+testKey, testTimeout, testLog) @@ -150,6 +160,7 @@ func TestFromFile(t *testing.T) { } // ToDo: rate limit writing n, err := s.Write(video) + if err != nil { t.Errorf("Session.Write failed with error: %v", err) } From 2333d1953e4593f843c1642315b51915f7e82e87 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 13:23:12 +1030 Subject: [PATCH 12/34] packetSize -> headerSizes. --- rtmp/packet.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 79bd99fe..6d33731c 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -68,8 +68,12 @@ const ( RTMP_CHANNEL_SOURCE = 0x04 ) -// packetSize defines valid packet sizes. -var packetSize = [...]int{12, 8, 4, 1} +// headerSizes defines header sizes for header types 0, 1, 2 and 3 respectively: +// 0: full header (12 bytes) +// 1: header without message ID (8 bytes) +// 2: basic header + timestamp (4 byes) +// 3: basic header (chunk type and stream ID) (1 byte) +var headerSizes = [...]int{12, 8, 4, 1} // packet defines an RTMP packet. type packet struct { @@ -154,7 +158,7 @@ func readPacket(s *Session, pkt *packet) error { s.channelsAllocatedIn = n } - size := packetSize[pkt.headerType] + size := headerSizes[pkt.headerType] switch { case size == RTMP_LARGE_HEADER_SIZE: pkt.hasAbsTimestamp = true @@ -308,7 +312,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { if pkt.body != nil { // Span from -packetsize for the type to the start of the body. headBytes = pkt.header - origIdx = RTMP_MAX_HEADER_SIZE - packetSize[pkt.headerType] + origIdx = RTMP_MAX_HEADER_SIZE - headerSizes[pkt.headerType] } else { // Allocate a new header and allow 6 bytes of movement backward. var hbuf [RTMP_MAX_HEADER_SIZE]byte @@ -324,7 +328,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { cSize = 1 } - hSize := packetSize[pkt.headerType] + hSize := headerSizes[pkt.headerType] if cSize != 0 { origIdx -= cSize hSize += cSize @@ -365,7 +369,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { } } - if packetSize[pkt.headerType] > 1 { + if headerSizes[pkt.headerType] > 1 { res := ts if ts > 0xffffff { res = 0xffffff @@ -374,14 +378,14 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { headerIdx += 3 // 24bits } - if packetSize[pkt.headerType] > 4 { + if headerSizes[pkt.headerType] > 4 { C_AMF_EncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) headerIdx += 3 // 24bits headBytes[headerIdx] = pkt.packetType headerIdx++ } - if packetSize[pkt.headerType] > 8 { + if headerSizes[pkt.headerType] > 8 { n := int(encodeInt32LE(headBytes[headerIdx:headerIdx+4], pkt.info)) headerIdx += n } From d9c98a0341dbb06b57429416495a69ec3c469ed5 Mon Sep 17 00:00:00 2001 From: saxon Date: Thu, 10 Jan 2019 13:56:20 +1030 Subject: [PATCH 13/34] rtmp: added streaming from file testing --- rtmp/rtmp_test.go | 55 +++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index ea5f687c..0dd0e58c 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -33,6 +33,10 @@ import ( "os" "runtime" "testing" + "time" + + "bitbucket.org/ausocean/av/stream/flv" + "bitbucket.org/ausocean/av/stream/lex" ) const ( @@ -44,7 +48,7 @@ const ( ) // debug enables extra logging -var testDebug bool +var testDebug = true // testKey is the RTMP key required for YouTube streaming (RTMP_TEST_KEY env var) var testKey string @@ -81,7 +85,7 @@ func testLog(level int8, msg string, params ...interface{}) { func TestKey(t *testing.T) { testLog(0, "TestKey") - testKey := os.Getenv("RTMP_TEST_KEY") + testKey = os.Getenv("RTMP_TEST_KEY") if testKey == "" { t.Errorf("RTMP_TEST_KEY environment variable not defined") os.Exit(1) @@ -134,28 +138,43 @@ func TestOpenClose(t *testing.T) { func TestFromFile(t *testing.T) { testLog(0, "TestFromFile") - testFile := os.Getenv("RTMP_TEST_FILE") - if testKey == "" { - t.Errorf("RTMP_TEST_FILE environment variable not defined") - os.Exit(1) - } s := NewSession(testBaseURL+testKey, testTimeout, testLog) err := s.Open() if err != nil { t.Errorf("Session.Open failed with error: %v", err) } - video, err := ioutil.ReadFile(testFile) - if err != nil { - t.Errorf("Cannot open video file: %v", testFile) - } - // ToDo: rate limit writing - n, err := s.Write(video) - if err != nil { - t.Errorf("Session.Write failed with error: %v", err) - } - if n != len(video) { - t.Errorf("Session.Write retuned wrong length") + + // This read from a h264 file + if true { + // Open file + f, err := os.Open("../../test/test-data/av/input/betterInput.h264") + if err != nil { + t.Errorf("Cannot open video file: %v", testFile) + } + defer f.Close() + + // Passing rtmp session, true for audio, true for video, and 25 fps + flvEncoder := flv.NewEncoder(s, true, true, 25) + err = lex.H264(flvEncoder, f, time.Second/time.Duration(25)) + if err != nil { + t.Errorf("Lexing and encoding failed with error: %v", err) + } + + // This reads a single h264 frame and sends + } else { + b, err := ioutil.ReadFile("/home/saxon/Downloads/ausoceanFrame.h264") // b has type []byte + if err != nil { + t.Errorf("Could not read file, failed with error: %v", err) + } + flvEncoder := flv.NewEncoder(s, true, true, 25) + for i := 0; i < 10000; i++ { + err := flvEncoder.Encode(b) + if err != nil { + t.Errorf("Encoding failed!") + } + } } + err = s.Close() if err != nil { t.Errorf("Session.Close failed with error: %v", err) From a318f9c3ebdfa9df6a3d466e873c8e4322650437 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 15:33:24 +1030 Subject: [PATCH 14/34] Added media streaming tests and improved comments. --- rtmp/rtmp_test.go | 133 +++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 54 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 4f927138..296809c6 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -29,14 +29,15 @@ package rtmp import ( "fmt" + "io/ioutil" "os" + "path/filepath" "runtime" "testing" "time" "bitbucket.org/ausocean/av/stream/flv" "bitbucket.org/ausocean/av/stream/lex" - "io/ioutil" ) const ( @@ -45,10 +46,15 @@ const ( testApp = "live2" testBaseURL = rtmpProtocol + "://" + testHost + "/" + testApp + "/" testTimeout = 30 + testDataDir = "../../test/test-data/av/input" ) -// debug enables extra logging -var testDebug = true +// testVerbosity controls the amount of output +// NB: This is not the log level, which is DebugLevel. +// 0: suppress logging completely +// 1: log messages only +// 2: log messages with errors, if any +var testVerbosity = 1 // testKey is the RTMP key required for YouTube streaming (RTMP_TEST_KEY env var) var testKey string @@ -59,10 +65,14 @@ var testFile string // testLog is a bare bones logger that logs to stdout. func testLog(level int8, msg string, params ...interface{}) { logLevels := [...]string{"Debug", "Info", "Warn", "Error", "", "", "Fatal"} + if testVerbosity == 0 { + return + } if level < -1 || level > 5 { panic("Invalid log level") } - if testDebug && len(params) >= 2 { + if testVerbosity == 2 && len(params) >= 2 { + // extract the params we know about, otherwise just print the message switch params[0].(string) { case "error": fmt.Printf("%s: %s, error=%v\n", logLevels[level+1], msg, params[1].(string)) @@ -83,16 +93,19 @@ func testLog(level int8, msg string, params ...interface{}) { } } +// TestKey tests that the RTMP_TEST_KEY environment variable is present func TestKey(t *testing.T) { testLog(0, "TestKey") testKey = os.Getenv("RTMP_TEST_KEY") if testKey == "" { - fmt.Printf("RTMP_TEST_KEY environment variable not defined\n") - os.Exit(1) + msg := "RTMP_TEST_KEY environment variable not defined" + testLog(0, msg) + t.Skip(msg) } testLog(0, "Testing against URL "+testBaseURL+testKey) } +// TestSetupURL tests URL parsing. func TestSetupURL(t *testing.T) { testLog(0, "TestSetupURL") // test with just the base URL @@ -104,12 +117,6 @@ func TestSetupURL(t *testing.T) { if err != nil { t.Errorf("setupURL(testBaseURL) failed with error: %v", err) } - // test again with the full URL - s = NewSession(testBaseURL+testKey, testTimeout, testLog) - err = setupURL(s, s.url) - if err != nil { - t.Errorf("setupURL(testBaseURL+testKey) failed with error: %v", err) - } // test the parts are as expected if rtmpProtocolStrings[s.link.protocol] != rtmpProtocol { t.Errorf("setupURL returned wrong protocol: %v", s.link.protocol) @@ -122,66 +129,48 @@ func TestSetupURL(t *testing.T) { } } -func TestOpen(t *testing.T) { - testLog(0, "TestOpen") - s := NewSession(testBaseURL+testKey, testTimeout, testLog) - err := setupURL(s, s.url) - if err != nil { - t.Errorf("setupURL failed with error: %v", err) - } - s.enableWrite() - err = s.Open() - if err != nil { - t.Errorf("connect failed with error: %v", err) - } -} - +// TestOpenClose tests opening an closing an RTMP connection. func TestOpenClose(t *testing.T) { testLog(0, "TestOpenClose") + if testKey == "" { + t.Skip("Skipping TestOpenClose since no RTMP_TEST_KEY") + } s := NewSession(testBaseURL+testKey, testTimeout, testLog) err := s.Open() if err != nil { t.Errorf("Open failed with error: %v", err) return } + err = s.Close() + if err != nil { + t.Errorf("Close failed with error: %v", err) + return + } } -func TestFromFile(t *testing.T) { - testLog(0, "TestFromFile") +// TestFromFrame tests streaming from a single H.264 frame which is repeated. +func TestFromFrame(t *testing.T) { + testLog(0, "TestFromFrame") + if testKey == "" { + t.Skip("Skipping TestFromFrame since no RTMP_TEST_KEY") + } s := NewSession(testBaseURL+testKey, testTimeout, testLog) err := s.Open() if err != nil { t.Errorf("Session.Open failed with error: %v", err) } - // This read from a h264 file - if true { - // Open file - f, err := os.Open("../../test/test-data/av/input/betterInput.h264") - if err != nil { - t.Errorf("Cannot open video file: %v", testFile) - } - defer f.Close() + b, err := ioutil.ReadFile(filepath.Join(testDataDir, "AusOcean_logo_1080p.h264")) + if err != nil { + t.Errorf("ReadFile failed with error: %v", err) + } - // Passing rtmp session, true for audio, true for video, and 25 fps - flvEncoder := flv.NewEncoder(s, true, true, 25) - err = lex.H264(flvEncoder, f, time.Second/time.Duration(25)) + // Pass RTMP session, true for audio, true for video, and 25 FPS + flvEncoder := flv.NewEncoder(s, true, true, 25) + for i := 0; i < 25; i++ { + err := flvEncoder.Encode(b) if err != nil { - t.Errorf("Lexing and encoding failed with error: %v", err) - } - - // This reads a single h264 frame and sends - } else { - b, err := ioutil.ReadFile("/home/saxon/Downloads/ausoceanFrame.h264") // b has type []byte - if err != nil { - t.Errorf("Could not read file, failed with error: %v", err) - } - flvEncoder := flv.NewEncoder(s, true, true, 25) - for i := 0; i < 10000; i++ { - err := flvEncoder.Encode(b) - if err != nil { - t.Errorf("Encoding failed!") - } + t.Errorf("Encoding failed with error: %v", err) } } @@ -190,3 +179,39 @@ func TestFromFile(t *testing.T) { t.Errorf("Session.Close failed with error: %v", err) } } + +// TestFromFile tests streaming from an video file comprising raw H.264. +// The test file is supplied via the RTMP_TEST_FILE environment variable. +func TestFromFile(t *testing.T) { + testLog(0, "TestFromFile") + testFile := os.Getenv("RTMP_TEST_FILE") + if testFile == "" { + t.Skip("Skipping TestFromFile since no RTMP_TEST_FILE") + } + if testKey == "" { + t.Skip("Skipping TestFromFile since no RTMP_TEST_KEY") + } + s := NewSession(testBaseURL+testKey, testTimeout, testLog) + err := s.Open() + if err != nil { + t.Errorf("Session.Open failed with error: %v", err) + } + + f, err := os.Open(testFile) + if err != nil { + t.Errorf("Open failed with error: %v", err) + } + defer f.Close() + + // Pass RTMP session, true for audio, true for video, and 25 FPS + flvEncoder := flv.NewEncoder(s, true, true, 25) + err = lex.H264(flvEncoder, f, time.Second/time.Duration(25)) + if err != nil { + t.Errorf("Lexing and encoding failed with error: %v", err) + } + + err = s.Close() + if err != nil { + t.Errorf("Session.Close failed with error: %v", err) + } +} From 3c83ba0022dddd8f8a6905ffe49d64bc5431cf42 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 16:34:00 +1030 Subject: [PATCH 15/34] Added RTMP_PACKET_SIZE_AUTO. --- rtmp/packet.go | 19 ++++++++++++++++++- rtmp/rtmp_headers.go | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 6d33731c..c3dff96b 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -60,6 +60,7 @@ const ( RTMP_PACKET_SIZE_MEDIUM = 1 RTMP_PACKET_SIZE_SMALL = 2 RTMP_PACKET_SIZE_MINIMUM = 3 + RTMP_PACKET_SIZE_AUTO = 4 ) const ( @@ -260,9 +261,25 @@ func readPacket(s *Session, pkt *packet) error { // resizePacket adjusts the packet's storage to accommodate a body of the given size. func resizePacket(pkt *packet, size uint32, ht uint8) { buf := make([]byte, RTMP_MAX_HEADER_SIZE+size) - pkt.headerType = ht pkt.header = buf pkt.body = buf[RTMP_MAX_HEADER_SIZE:] + if ht != RTMP_PACKET_SIZE_AUTO { + pkt.headerType = ht + return + } + switch pkt.packetType { + case RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_AUDIO: + if pkt.timestamp == 0 { + pkt.headerType = RTMP_PACKET_SIZE_LARGE + } else { + pkt.headerType = RTMP_PACKET_SIZE_MEDIUM + } + case RTMP_PACKET_TYPE_INFO: + pkt.headerType = RTMP_PACKET_SIZE_LARGE + pkt.bodySize += 16 + default: + pkt.headerType = RTMP_PACKET_SIZE_MEDIUM + } } // sendPacket sends a packet. diff --git a/rtmp/rtmp_headers.go b/rtmp/rtmp_headers.go index becb48be..d50675b5 100644 --- a/rtmp/rtmp_headers.go +++ b/rtmp/rtmp_headers.go @@ -91,7 +91,7 @@ const ( RTMP_BUFFER_CACHE_SIZE = (16 * 1024) RTMP_SIG_SIZE = 1536 RTMP_LARGE_HEADER_SIZE = 12 - RTMP_MAX_HEADER_SIZE = 18 + RTMP_MAX_HEADER_SIZE = RTMP_LARGE_HEADER_SIZE ) type link struct { From e6504734d0692c5ce087c92c6a171e49b6c2dbfa Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 21:22:00 +1030 Subject: [PATCH 16/34] Rate limit TestFromFrame output to 25 FPS. --- rtmp/rtmp_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 296809c6..949ebeef 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -172,6 +172,7 @@ func TestFromFrame(t *testing.T) { if err != nil { t.Errorf("Encoding failed with error: %v", err) } + time.Sleep(40 * time.Millisecond) // rate limit to 1/25s } err = s.Close() From 56a3bf8b26d4c753e987b4b5c2a90491fd901982 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 22:52:43 +1030 Subject: [PATCH 17/34] Simplified Session.write(). --- rtmp/rtmp.go | 86 ++++++++++------------------------------------------ 1 file changed, 16 insertions(+), 70 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 2c6300d5..ea5d05cb 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -130,7 +130,7 @@ var ( errTinyPacket = errors.New("rtmp: packet too small") errEncoding = errors.New("rtmp: encoding error") errDecoding = errors.New("rtmp: decoding error") - errCopying = errors.New("rtmp: copying error") + errUnimplemented = errors.New("rtmp: unimplemented feature") ) // setupURL parses the RTMP URL. @@ -861,76 +861,22 @@ func handshake(s *Session) error { // write prepares data to write then sends it. func (s *Session) write(buf []byte) error { + if buf[0] == RTMP_PACKET_TYPE_INFO || (buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V') { + return errUnimplemented + } + if len(buf) < minDataSize { + return errTinyPacket + } + pkt := packet{ - channel: RTMP_CHANNEL_SOURCE, - info: s.streamID, + packetType: buf[0], + bodySize: C_AMF_DecodeInt24(buf[1:4]), + timestamp: C_AMF_DecodeInt24(buf[4:7]) | uint32(buf[7])<<24, + channel: RTMP_CHANNEL_SOURCE, + info: s.streamID, } - var enc []byte - for len(buf) != 0 { - if pkt.bytesRead == 0 { - if len(buf) < minDataSize { - return errTinyPacket - } - - if buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V' { - buf = buf[13:] - } - - pkt.packetType = buf[0] - buf = buf[1:] - pkt.bodySize = C_AMF_DecodeInt24(buf[:3]) - buf = buf[3:] - pkt.timestamp = C_AMF_DecodeInt24(buf[:3]) - buf = buf[3:] - pkt.timestamp |= uint32(buf[0]) << 24 - buf = buf[4:] - - headerType := uint8(RTMP_PACKET_SIZE_MEDIUM) - switch pkt.packetType { - case RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_AUDIO: - if pkt.timestamp == 0 { - headerType = RTMP_PACKET_SIZE_LARGE - } - case RTMP_PACKET_TYPE_INFO: - headerType = RTMP_PACKET_SIZE_LARGE - pkt.bodySize += 16 - } - - resizePacket(&pkt, pkt.bodySize, headerType) - - enc = pkt.body[:pkt.bodySize] - if pkt.packetType == RTMP_PACKET_TYPE_INFO { - enc = C_AMF_EncodeString(enc, setDataFrame) - if enc == nil { - return errEncoding - } - pkt.bytesRead = uint32(len(pkt.body) - len(enc)) - } - - } else { - enc = pkt.body[:pkt.bodySize][pkt.bytesRead:] - } - num := int(pkt.bodySize - pkt.bytesRead) - if num > len(buf) { - num = len(buf) - } - - copy(enc[:num], buf[:num]) - pkt.bytesRead += uint32(num) - buf = buf[num:] - if pkt.bytesRead == pkt.bodySize { - err := sendPacket(s, &pkt, false) - pkt.body = nil - pkt.bytesRead = 0 - if err != nil { - return err - } - if len(buf) < 4 { - return nil - } - buf = buf[4:] - } - } - return nil + resizePacket(&pkt, pkt.bodySize, RTMP_PACKET_SIZE_AUTO) + copy(pkt.body, buf[minDataSize:minDataSize+pkt.bodySize]) + return sendPacket(s, &pkt, false) } From 26e8133a6e9de0042d12454cc8d3a097d4f76bbe Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 23:05:24 +1030 Subject: [PATCH 18/34] Merge Session.write() into Session.Write(). --- rtmp/rtmp.go | 22 ---------------------- rtmp/session.go | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index ea5d05cb..1216058c 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -858,25 +858,3 @@ func handshake(s *Session) error { } return nil } - -// write prepares data to write then sends it. -func (s *Session) write(buf []byte) error { - if buf[0] == RTMP_PACKET_TYPE_INFO || (buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V') { - return errUnimplemented - } - if len(buf) < minDataSize { - return errTinyPacket - } - - pkt := packet{ - packetType: buf[0], - bodySize: C_AMF_DecodeInt24(buf[1:4]), - timestamp: C_AMF_DecodeInt24(buf[4:7]) | uint32(buf[7])<<24, - channel: RTMP_CHANNEL_SOURCE, - info: s.streamID, - } - - resizePacket(&pkt, pkt.bodySize, RTMP_PACKET_SIZE_AUTO) - copy(pkt.body, buf[minDataSize:minDataSize+pkt.bodySize]) - return sendPacket(s, &pkt, false) -} diff --git a/rtmp/session.go b/rtmp/session.go index a402a320..6a994919 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -159,7 +159,24 @@ func (s *Session) Write(data []byte) (int, error) { if !s.isConnected() { return 0, errNotConnected } - err := s.write(data) + if data[0] == RTMP_PACKET_TYPE_INFO || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { + return 0, errUnimplemented + } + if len(data) < minDataSize { + return 0, errTinyPacket + } + + pkt := packet{ + packetType: data[0], + bodySize: C_AMF_DecodeInt24(data[1:4]), + timestamp: C_AMF_DecodeInt24(data[4:7]) | uint32(data[7])<<24, + channel: RTMP_CHANNEL_SOURCE, + info: s.streamID, + } + + resizePacket(&pkt, pkt.bodySize, RTMP_PACKET_SIZE_AUTO) + copy(pkt.body, data[minDataSize:minDataSize+pkt.bodySize]) + err := sendPacket(s, &pkt, false) if err != nil { return 0, err } From 6a8e78a2569701a4a77e2c31a0f27f2d24129574 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 23:16:20 +1030 Subject: [PATCH 19/34] readN()/writeN() now Session.read()/write() respectfully. --- rtmp/packet.go | 18 +++++++++--------- rtmp/rtmp.go | 45 +++++---------------------------------------- rtmp/session.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index c3dff96b..0c272657 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -35,8 +35,8 @@ LICENSE package rtmp import ( - "io" "encoding/binary" + "io" ) const ( @@ -104,7 +104,7 @@ func readPacket(s *Session, pkt *packet) error { var hbuf [RTMP_MAX_HEADER_SIZE]byte header := hbuf[:] - err := readN(s, header[:1]) + err := s.read(header[:1]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 1st byte", "error", err.Error()) if err == io.EOF { @@ -118,7 +118,7 @@ func readPacket(s *Session, pkt *packet) error { switch { case pkt.channel == 0: - err = readN(s, header[:1]) + err = s.read(header[:1]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 2nd byte", "error", err.Error()) return err @@ -127,7 +127,7 @@ func readPacket(s *Session, pkt *packet) error { pkt.channel = int32(header[0]) + 64 case pkt.channel == 1: - err = readN(s, header[:2]) + err = s.read(header[:2]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 3rd byte", "error", err.Error()) return err @@ -171,7 +171,7 @@ func readPacket(s *Session, pkt *packet) error { size-- if size > 0 { - err = readN(s, header[:size]) + err = s.read(header[:size]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header", "error", err.Error()) return err @@ -197,7 +197,7 @@ func readPacket(s *Session, pkt *packet) error { extendedTimestamp := pkt.timestamp == 0xffffff if extendedTimestamp { - err = readN(s, header[size:size+4]) + err = s.read(header[size : size+4]) if err != nil { s.log(DebugLevel, pkg+"failed to read extended timestamp", "error", err.Error()) return err @@ -224,7 +224,7 @@ func readPacket(s *Session, pkt *packet) error { pkt.chunk.data = pkt.body[pkt.bytesRead : pkt.bytesRead+uint32(chunkSize)] } - err = readN(s, pkt.body[pkt.bytesRead:][:chunkSize]) + err = s.read(pkt.body[pkt.bytesRead:][:chunkSize]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet body", "error", err.Error()) return err @@ -418,7 +418,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { s.log(DebugLevel, pkg+"sending packet", "size", size, "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr()) if s.deferred != nil && len(s.deferred)+size+hSize > chunkSize { - err := writeN(s, s.deferred) + err := s.write(s.deferred) if err != nil { return err } @@ -441,7 +441,7 @@ func sendPacket(s *Session, pkt *packet, queue bool) error { // Prepend the previously deferred packet and write it with the current one. bytes = append(s.deferred, bytes...) } - err := writeN(s, bytes) + err := s.write(bytes) if err != nil { return err } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 1216058c..b38a1a1a 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -38,7 +38,6 @@ import ( "bytes" "encoding/binary" "errors" - "io" "math/rand" "net" "strconv" @@ -276,40 +275,6 @@ func handlePacket(s *Session, pkt *packet) error { return nil } -func readN(s *Session, buf []byte) error { - err := s.link.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) - if err != nil { - return err - } - n, err := io.ReadFull(s.link.conn, buf) - if err != nil { - s.log(DebugLevel, pkg+"read failed", "error", err.Error()) - return err - } - s.nBytesIn += int32(n) - if s.nBytesIn > (s.nBytesInSent + s.clientBW/10) { - err := sendBytesReceived(s) - if err != nil { - return err - } - } - return nil -} - -func writeN(s *Session, buf []byte) error { - //ToDo: consider using a different timeout for writes than for reads - err := s.link.conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) - if err != nil { - return err - } - _, err = s.link.conn.Write(buf) - if err != nil { - s.log(WarnLevel, pkg+"write failed", "error", err.Error()) - return err - } - return nil -} - func sendConnectPacket(s *Session) error { var pbuf [4096]byte pkt := packet{ @@ -817,14 +782,14 @@ func handshake(s *Session) error { clientsig[i] = byte(rand.Intn(256)) } - err := writeN(s, clientbuf[:]) + err := s.write(clientbuf[:]) if err != nil { return err } s.log(DebugLevel, pkg+"handshake sent") var typ [1]byte - err = readN(s, typ[:]) + err = s.read(typ[:]) if err != nil { return err } @@ -833,7 +798,7 @@ func handshake(s *Session) error { if typ[0] != clientbuf[0] { s.log(WarnLevel, pkg+"handshake type mismatch", "sent", clientbuf[0], "received", typ) } - err = readN(s, serversig[:]) + err = s.read(serversig[:]) if err != nil { return err } @@ -843,12 +808,12 @@ func handshake(s *Session) error { s.log(DebugLevel, pkg+"server uptime", "uptime", suptime) // 2nd part of handshake - err = writeN(s, serversig[:]) + err = s.write(serversig[:]) if err != nil { return err } - err = readN(s, serversig[:]) + err = s.read(serversig[:]) if err != nil { return err } diff --git a/rtmp/session.go b/rtmp/session.go index 6a994919..2aa62fe0 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -33,6 +33,11 @@ LICENSE */ package rtmp +import ( + "io" + "time" +) + // Session holds the state for an RTMP session. type Session struct { url string @@ -183,6 +188,43 @@ func (s *Session) Write(data []byte) (int, error) { return len(data), nil } +// I/O functions +// read from an RTMP connection. +func (s *Session) read(buf []byte) error { + err := s.link.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) + if err != nil { + return err + } + n, err := io.ReadFull(s.link.conn, buf) + if err != nil { + s.log(DebugLevel, pkg+"read failed", "error", err.Error()) + return err + } + s.nBytesIn += int32(n) + if s.nBytesIn > (s.nBytesInSent + s.clientBW/10) { + err := sendBytesReceived(s) + if err != nil { + return err + } + } + return nil +} + +// write to an RTMP connection. +func (s *Session) write(buf []byte) error { + //ToDo: consider using a different timeout for writes than for reads + err := s.link.conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) + if err != nil { + return err + } + _, err = s.link.conn.Write(buf) + if err != nil { + s.log(WarnLevel, pkg+"write failed", "error", err.Error()) + return err + } + return nil +} + // isConnected returns true if the RTMP connection is up. func (s *Session) isConnected() bool { return s.link.conn != nil From 058cc4135686eb7803e2b68424a3728b1e3e8607 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 23:29:51 +1030 Subject: [PATCH 20/34] readPacket(), sendPacket() and resizePacket() now Packet methods read(), write() and resize() respectively. --- rtmp/packet.go | 14 +++++++------- rtmp/rtmp.go | 20 ++++++++++---------- rtmp/session.go | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 0c272657..99c9e029 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -99,8 +99,8 @@ type chunk struct { } // ToDo: Consider making the following functions into methods. -// readPacket reads a packet. -func readPacket(s *Session, pkt *packet) error { +// read reads a packet. +func (pkt *packet) read(s *Session) error { var hbuf [RTMP_MAX_HEADER_SIZE]byte header := hbuf[:] @@ -208,7 +208,7 @@ func readPacket(s *Session, pkt *packet) error { } if pkt.bodySize > 0 && pkt.body == nil { - resizePacket(pkt, pkt.bodySize, (hbuf[0]&0xc0)>>6) + pkt.resize(pkt.bodySize, (hbuf[0]&0xc0)>>6) } toRead := int32(pkt.bodySize - pkt.bytesRead) @@ -258,8 +258,8 @@ func readPacket(s *Session, pkt *packet) error { return nil } -// resizePacket adjusts the packet's storage to accommodate a body of the given size. -func resizePacket(pkt *packet, size uint32, ht uint8) { +// resize adjusts the packet's storage to accommodate a body of the given size. +func (pkt *packet) resize(size uint32, ht uint8) { buf := make([]byte, RTMP_MAX_HEADER_SIZE+size) pkt.header = buf pkt.body = buf[RTMP_MAX_HEADER_SIZE:] @@ -282,8 +282,8 @@ func resizePacket(pkt *packet, size uint32, ht uint8) { } } -// sendPacket sends a packet. -func sendPacket(s *Session, pkt *packet, queue bool) error { +// write sends a packet. +func (pkt *packet) write(s *Session, queue bool) error { var prevPkt *packet var last int diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index b38a1a1a..791c1b93 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -193,7 +193,7 @@ func connectStream(s *Session) error { var err error for !s.isPlaying && s.isConnected() { pkt := packet{} - err = readPacket(s, &pkt) + err = pkt.read(s) if err != nil { break } @@ -389,7 +389,7 @@ func sendConnectPacket(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) // response expected + return pkt.write(s, true) // response expected } func sendCreateStream(s *Session) error { @@ -417,7 +417,7 @@ func sendCreateStream(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) // response expected + return pkt.write(s, true) // response expected } func sendReleaseStream(s *Session) error { @@ -448,7 +448,7 @@ func sendReleaseStream(s *Session) error { } pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } func sendFCPublish(s *Session) error { @@ -480,7 +480,7 @@ func sendFCPublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } func sendFCUnpublish(s *Session) error { @@ -512,7 +512,7 @@ func sendFCUnpublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } func sendPublish(s *Session) error { @@ -548,7 +548,7 @@ func sendPublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, true) // response expected + return pkt.write(s, true) // response expected } func sendDeleteStream(s *Session, dStreamId float64) error { @@ -580,7 +580,7 @@ func sendDeleteStream(s *Session, dStreamId float64) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) /* no response expected */ - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } // sendBytesReceived tells the server how many bytes the client has received. @@ -602,7 +602,7 @@ func sendBytesReceived(s *Session) error { } pkt.bodySize = 4 - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } func sendCheckBW(s *Session) error { @@ -630,7 +630,7 @@ func sendCheckBW(s *Session) error { pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) - return sendPacket(s, &pkt, false) + return pkt.write(s, false) } func eraseMethod(m []method, i int) []method { diff --git a/rtmp/session.go b/rtmp/session.go index 2aa62fe0..e09b3caa 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -179,9 +179,9 @@ func (s *Session) Write(data []byte) (int, error) { info: s.streamID, } - resizePacket(&pkt, pkt.bodySize, RTMP_PACKET_SIZE_AUTO) + pkt.resize(pkt.bodySize, RTMP_PACKET_SIZE_AUTO) copy(pkt.body, data[minDataSize:minDataSize+pkt.bodySize]) - err := sendPacket(s, &pkt, false) + err := pkt.write(s, false) if err != nil { return 0, err } From f635be6712e8c5979f7fc18e162c8f96d799f911 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 10 Jan 2019 23:48:11 +1030 Subject: [PATCH 21/34] Propagate errors from handleInvoke(). --- rtmp/rtmp.go | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 791c1b93..4d432a00 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -121,6 +121,7 @@ var ( errUnknownScheme = errors.New("rtmp: unknown scheme") errConnected = errors.New("rtmp: already connected") errNotConnected = errors.New("rtmp: not connected") + errNotWritable = errors.New("rtmp: connection not writable") errHandshake = errors.New("rtmp: handshake failed") errConnSend = errors.New("rtmp: connection send error") errConnStream = errors.New("rtmp: connection stream error") @@ -675,27 +676,31 @@ func handleInvoke(s *Session, body []byte) error { case av_connect: if s.link.token != "" { s.log(FatalLevel, pkg+"no support for link token") - } - if (s.link.protocol & RTMP_FEATURE_WRITE) != 0 { - sendReleaseStream(s) - sendFCPublish(s) - } else { - s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") - } - - sendCreateStream(s) if (s.link.protocol & RTMP_FEATURE_WRITE) == 0 { - s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") + return errNotWritable + } + err := sendReleaseStream(s) + if err != nil { + return err + } + err = sendFCPublish(s) + if err != nil { + return err + } + err = sendCreateStream(s) + if err != nil { + return err } case av_createStream: s.streamID = int32(C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 3))) - - if s.link.protocol&RTMP_FEATURE_WRITE != 0 { - sendPublish(s) - } else { - s.log(FatalLevel, pkg+"link protocol has no RTMP_FEATURE_WRITE") + if s.link.protocol&RTMP_FEATURE_WRITE == 0 { + return errNotWritable + } + err := sendPublish(s) + if err != nil { + return err } case av_play, av_publish: @@ -704,7 +709,10 @@ func handleInvoke(s *Session, body []byte) error { case av_onBWDone: if s.checkCounter == 0 { // ToDo: why is this always zero? - sendCheckBW(s) + err := sendCheckBW(s) + if err != nil { + return err + } } case av_onFCUnsubscribe, av_onFCSubscribe: From 53bccaaaa75897e916149d312ec50d52735ae868 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:01:26 +1030 Subject: [PATCH 22/34] Simplifed connectStream(). --- rtmp/rtmp.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 4d432a00..a481aa84 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -192,22 +192,19 @@ func connect(s *Session) error { // connectStream reads a packet and handles it func connectStream(s *Session) error { var err error - for !s.isPlaying && s.isConnected() { + for !s.isPlaying { pkt := packet{} err = pkt.read(s) if err != nil { break } - if pkt.bodySize == 0 { - continue - } - if pkt.packetType == RTMP_PACKET_TYPE_AUDIO || - pkt.packetType == RTMP_PACKET_TYPE_VIDEO || - pkt.packetType == RTMP_PACKET_TYPE_INFO { - s.log(DebugLevel, pkg+"got packet before play; ignoring") - continue - } + switch pkt.packetType { + case RTMP_PACKET_TYPE_AUDIO, RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_INFO: + s.log(WarnLevel, pkg+"got packet before play; ignoring", "type", pkt.packetType) + default: + handlePacket(s, &pkt) // ignore errors + } err = handlePacket(s, &pkt) if err != nil { break From b388f5374f4ef105b39289484e4f317029842eda Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:06:44 +1030 Subject: [PATCH 23/34] Removed superfluous url param from setupURL() and obtain it from the Session object instead. --- rtmp/rtmp.go | 6 +++--- rtmp/rtmp_test.go | 2 +- rtmp/session.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index a481aa84..bacf300a 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -134,8 +134,8 @@ var ( ) // setupURL parses the RTMP URL. -func setupURL(s *Session, addr string) (err error) { - s.link.protocol, s.link.host, s.link.port, s.link.app, s.link.playpath, err = parseURL(addr) +func setupURL(s *Session) (err error) { + s.link.protocol, s.link.host, s.link.port, s.link.app, s.link.playpath, err = parseURL(s.url) if err != nil { return err } @@ -145,7 +145,7 @@ func setupURL(s *Session, addr string) (err error) { s.link.tcUrl = rtmpProtocolStrings[s.link.protocol] + "://" + s.link.host + ":" + strconv.Itoa(int(s.link.port)) + "/" + s.link.app s.link.lFlags |= RTMP_LF_FTCU } else { - s.link.tcUrl = addr + s.link.tcUrl = s.url } } diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 949ebeef..6e07535a 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -113,7 +113,7 @@ func TestSetupURL(t *testing.T) { if s.url != testBaseURL && s.link.timeout != testTimeout { t.Errorf("NewSession failed") } - err := setupURL(s, s.url) + err := setupURL(s) if err != nil { t.Errorf("setupURL(testBaseURL) failed with error: %v", err) } diff --git a/rtmp/session.go b/rtmp/session.go index e09b3caa..4b2538a5 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -113,7 +113,7 @@ func (s *Session) Open() error { // start does the heavylifting for Open(). func (s *Session) start() error { s.log(DebugLevel, pkg+"Session.start") - err := setupURL(s, s.url) + err := setupURL(s) if err != nil { s.close() return err From 7bb9fcc2c7a625180e933f072c9a6fdfd07abda4 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:10:29 +1030 Subject: [PATCH 24/34] Remove superfluous call to handlePacket() from connectStream(). --- rtmp/rtmp.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index bacf300a..e200387a 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -203,11 +203,10 @@ func connectStream(s *Session) error { case RTMP_PACKET_TYPE_AUDIO, RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_INFO: s.log(WarnLevel, pkg+"got packet before play; ignoring", "type", pkt.packetType) default: - handlePacket(s, &pkt) // ignore errors - } - err = handlePacket(s, &pkt) - if err != nil { - break + err = handlePacket(s, &pkt) + if err != nil { + break + } } } @@ -221,7 +220,6 @@ func connectStream(s *Session) error { // NB: cases have been commented out that are not currently used by AusOcean func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { - case RTMP_PACKET_TYPE_CHUNK_SIZE: if pkt.bodySize >= 4 { s.inChunkSize = int32(C_AMF_DecodeInt32(pkt.body[:4])) From 94446beddbc9ce48c333dfaf88f71c40eeb6cb4f Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:19:50 +1030 Subject: [PATCH 25/34] Split remaining code in rtmp_headers across rtmp.go and session.go. --- rtmp/rtmp.go | 59 ++++++++++++++++++++++++++++++- rtmp/rtmp_headers.go | 82 -------------------------------------------- rtmp/session.go | 29 +++++++++++++++- 3 files changed, 86 insertions(+), 84 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index e200387a..e9392019 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -3,7 +3,7 @@ NAME rtmp.go DESCRIPTION - See Readme.md + RTMP command functionality. AUTHORS Saxon Nelson-Milton @@ -49,6 +49,63 @@ const ( minDataSize = 11 ) +const ( + RTMPT_OPEN = iota + RTMPT_SEND + RTMPT_IDLE + RTMPT_CLOSE +) + +const ( + RTMP_READ_HEADER = 0x01 + RTMP_READ_RESUME = 0x02 + RTMP_READ_NO_IGNORE = 0x04 + RTMP_READ_GOTKF = 0x08 + RTMP_READ_GOTFLVK = 0x10 + RTMP_READ_SEEKING = 0x20 + RTMP_READ_COMPLETE = -3 + RTMP_READ_ERROR = -2 + RTMP_READ_EOF = -1 + RTMP_READ_IGNORE = 0 +) + +const ( + RTMP_LF_AUTH = 0x0001 /* using auth param */ + RTMP_LF_LIVE = 0x0002 /* stream is live */ + RTMP_LF_SWFV = 0x0004 /* do SWF verification */ + RTMP_LF_PLST = 0x0008 /* send playlist before play */ + RTMP_LF_BUFX = 0x0010 /* toggle stream on BufferEmpty msg */ + RTMP_LF_FTCU = 0x0020 /* free tcUrl on close */ + RTMP_LF_FAPU = 0x0040 /* free app on close */ +) + +const ( + RTMP_FEATURE_HTTP = 0x01 + RTMP_FEATURE_ENC = 0x02 + RTMP_FEATURE_SSL = 0x04 + RTMP_FEATURE_MFP = 0x08 /* not yet supported */ + RTMP_FEATURE_WRITE = 0x10 /* publish, not play */ + RTMP_FEATURE_HTTP2 = 0x20 /* server-side rtmpt */ +) + +const ( + RTMP_PROTOCOL_RTMP = 0 + RTMP_PROTOCOL_RTMPE = RTMP_FEATURE_ENC + RTMP_PROTOCOL_RTMPT = RTMP_FEATURE_HTTP + RTMP_PROTOCOL_RTMPS = RTMP_FEATURE_SSL + RTMP_PROTOCOL_RTMPTE = (RTMP_FEATURE_HTTP | RTMP_FEATURE_ENC) + RTMP_PROTOCOL_RTMPTS = (RTMP_FEATURE_HTTP | RTMP_FEATURE_SSL) + RTMP_PROTOCOL_RTMFP = RTMP_FEATURE_MFP +) + +const ( + RTMP_DEFAULT_CHUNKSIZE = 128 + RTMP_BUFFER_CACHE_SIZE = (16 * 1024) + RTMP_SIG_SIZE = 1536 + RTMP_LARGE_HEADER_SIZE = 12 + RTMP_MAX_HEADER_SIZE = RTMP_LARGE_HEADER_SIZE +) + const ( setDataFrame = "@setDataFrame" diff --git a/rtmp/rtmp_headers.go b/rtmp/rtmp_headers.go index d50675b5..9c4854c5 100644 --- a/rtmp/rtmp_headers.go +++ b/rtmp/rtmp_headers.go @@ -33,87 +33,5 @@ LICENSE */ package rtmp -import ( - "net" -) -const ( - RTMPT_OPEN = iota - RTMPT_SEND - RTMPT_IDLE - RTMPT_CLOSE -) -const ( - RTMP_READ_HEADER = 0x01 - RTMP_READ_RESUME = 0x02 - RTMP_READ_NO_IGNORE = 0x04 - RTMP_READ_GOTKF = 0x08 - RTMP_READ_GOTFLVK = 0x10 - RTMP_READ_SEEKING = 0x20 - RTMP_READ_COMPLETE = -3 - RTMP_READ_ERROR = -2 - RTMP_READ_EOF = -1 - RTMP_READ_IGNORE = 0 -) - -const ( - RTMP_LF_AUTH = 0x0001 /* using auth param */ - RTMP_LF_LIVE = 0x0002 /* stream is live */ - RTMP_LF_SWFV = 0x0004 /* do SWF verification */ - RTMP_LF_PLST = 0x0008 /* send playlist before play */ - RTMP_LF_BUFX = 0x0010 /* toggle stream on BufferEmpty msg */ - RTMP_LF_FTCU = 0x0020 /* free tcUrl on close */ - RTMP_LF_FAPU = 0x0040 /* free app on close */ -) - -const ( - RTMP_FEATURE_HTTP = 0x01 - RTMP_FEATURE_ENC = 0x02 - RTMP_FEATURE_SSL = 0x04 - RTMP_FEATURE_MFP = 0x08 /* not yet supported */ - RTMP_FEATURE_WRITE = 0x10 /* publish, not play */ - RTMP_FEATURE_HTTP2 = 0x20 /* server-side rtmpt */ -) - -const ( - RTMP_PROTOCOL_RTMP = 0 - RTMP_PROTOCOL_RTMPE = RTMP_FEATURE_ENC - RTMP_PROTOCOL_RTMPT = RTMP_FEATURE_HTTP - RTMP_PROTOCOL_RTMPS = RTMP_FEATURE_SSL - RTMP_PROTOCOL_RTMPTE = (RTMP_FEATURE_HTTP | RTMP_FEATURE_ENC) - RTMP_PROTOCOL_RTMPTS = (RTMP_FEATURE_HTTP | RTMP_FEATURE_SSL) - RTMP_PROTOCOL_RTMFP = RTMP_FEATURE_MFP -) - -const ( - RTMP_DEFAULT_CHUNKSIZE = 128 - RTMP_BUFFER_CACHE_SIZE = (16 * 1024) - RTMP_SIG_SIZE = 1536 - RTMP_LARGE_HEADER_SIZE = 12 - RTMP_MAX_HEADER_SIZE = RTMP_LARGE_HEADER_SIZE -) - -type link struct { - host string - playpath string - tcUrl string - swfUrl string - pageUrl string - app string - auth string - flashVer string - token string - extras C_AMFObject - lFlags int32 - swfAge int32 - protocol int32 - timeout uint - port uint16 - conn *net.TCPConn -} - -type method struct { - name string - num int32 -} diff --git a/rtmp/session.go b/rtmp/session.go index 4b2538a5..2ea8e05f 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -3,7 +3,7 @@ NAME session.go DESCRIPTION - See Readme.md + RTMP session functionality. AUTHORS Saxon Nelson-Milton @@ -35,6 +35,7 @@ package rtmp import ( "io" + "net" "time" ) @@ -67,6 +68,32 @@ type Session struct { log Log } +// link represents RTMP URL and connection information. +type link struct { + host string + playpath string + tcUrl string + swfUrl string + pageUrl string + app string + auth string + flashVer string + token string + extras C_AMFObject + lFlags int32 + swfAge int32 + protocol int32 + timeout uint + port uint16 + conn *net.TCPConn +} + +// method represents an RTMP method. +type method struct { + name string + num int32 +} + // Log defines the RTMP logging function. type Log func(level int8, message string, params ...interface{}) From d4a0fad2bed5d3ad5cce9bf0995192048794471f Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:22:32 +1030 Subject: [PATCH 26/34] Removed now unused rtmp_headers.go. --- rtmp/rtmp_headers.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 rtmp/rtmp_headers.go diff --git a/rtmp/rtmp_headers.go b/rtmp/rtmp_headers.go deleted file mode 100644 index 9c4854c5..00000000 --- a/rtmp/rtmp_headers.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -NAME - rtmp_headers.go - -DESCRIPTION - See Readme.md - -AUTHORS - Saxon Nelson-Milton - Dan Kortschak - Alan Noble - -LICENSE - rtmp_headers.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - It is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. - - You should have received a copy of the GNU General Public License - along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. - - Derived from librtmp under the GNU Lesser General Public License 2.1 - Copyright (C) 2005-2008 Team XBMC http://www.xbmc.org - Copyright (C) 2008-2009 Andrej Stepanchuk - Copyright (C) 2009-2010 Howard Chu -*/ -package rtmp - - - From b3b1b048147d764eca8f903556f9d35456494af7 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:29:51 +1030 Subject: [PATCH 27/34] Merged Session.start()/close() into Open()/Close() respectively. --- rtmp/session.go | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/rtmp/session.go b/rtmp/session.go index 2ea8e05f..aa1ee80b 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -127,63 +127,46 @@ func NewSession(url string, timeout uint, log Log) *Session { // Open establishes an rtmp connection with the url passed into the constructor. func (s *Session) Open() error { + s.log(DebugLevel, pkg+"Session.Open") if s.isConnected() { return errConnected } - err := s.start() - if err != nil { - return err - } - return nil -} - -// start does the heavylifting for Open(). -func (s *Session) start() error { - s.log(DebugLevel, pkg+"Session.start") err := setupURL(s) if err != nil { - s.close() return err } s.enableWrite() err = connect(s) if err != nil { - s.close() + s.Close() return err } err = connectStream(s) if err != nil { - s.close() + s.Close() return err } return nil } -// Close terminates the rtmp connection, +// Close terminates the rtmp connection. +// NB: The session object is cleared completely. func (s *Session) Close() error { + s.log(DebugLevel, pkg+"Session.Close") if !s.isConnected() { return errNotConnected } - s.close() - return nil -} - -// close does the heavylifting for Close(). -// Any errors are ignored as it is often called in response to an earlier error. -func (s *Session) close() { - s.log(DebugLevel, pkg+"Session.close") - if s.isConnected() { - if s.streamID > 0 { - if s.link.protocol&RTMP_FEATURE_WRITE != 0 { - sendFCUnpublish(s) - } - sendDeleteStream(s, float64(s.streamID)) + if s.streamID > 0 { + if s.link.protocol&RTMP_FEATURE_WRITE != 0 { + sendFCUnpublish(s) } - s.link.conn.Close() + sendDeleteStream(s, float64(s.streamID)) } + s.link.conn.Close() *s = Session{} + return nil } // Write writes a frame (flv tag) to the rtmp connection. @@ -216,7 +199,10 @@ func (s *Session) Write(data []byte) (int, error) { } // I/O functions -// read from an RTMP connection. + +// read from an RTMP connection. Sends a bytes received message if the +// number of bytes received (nBytesIn) is greater than the number sent +// (nBytesInSent) by 10% of the bandwidth. func (s *Session) read(buf []byte) error { err := s.link.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) if err != nil { From 82522643bb5b645d2f0434a074dc2cde6bab691f Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 00:41:28 +1030 Subject: [PATCH 28/34] First cut at refactoring packet.write(). --- rtmp/packet.go | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 99c9e029..4fc2141b 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -3,7 +3,7 @@ NAME packet.go DESCRIPTION - See Readme.md + RTMP packet functionality. AUTHORS Saxon Nelson-Milton @@ -284,10 +284,12 @@ func (pkt *packet) resize(size uint32, ht uint8) { // write sends a packet. func (pkt *packet) write(s *Session, queue bool) error { - var prevPkt *packet - var last int + if pkt.body == nil { + return errInvalidBody + } if pkt.channel >= s.channelsAllocatedOut { + s.log(DebugLevel, pkg+"growing channelsOut", "channel", pkt.channel) n := int(pkt.channel + 10) var pkts []*packet @@ -304,8 +306,9 @@ func (pkt *packet) write(s *Session, queue bool) error { s.channelsAllocatedOut = int32(n) } - prevPkt = s.channelsOut[pkt.channel] + prevPkt := s.channelsOut[pkt.channel] + var last int if prevPkt != nil && pkt.headerType != RTMP_PACKET_SIZE_LARGE { // compress a bit by using the prev packet's attributes if prevPkt.bodySize == pkt.bodySize && prevPkt.packetType == pkt.packetType && pkt.headerType == RTMP_PACKET_SIZE_MEDIUM { @@ -324,20 +327,14 @@ func (pkt *packet) write(s *Session, queue bool) error { return errInvalidHeader } - var headBytes []byte - var origIdx int - if pkt.body != nil { - // Span from -packetsize for the type to the start of the body. - headBytes = pkt.header - origIdx = RTMP_MAX_HEADER_SIZE - headerSizes[pkt.headerType] - } else { - // Allocate a new header and allow 6 bytes of movement backward. - var hbuf [RTMP_MAX_HEADER_SIZE]byte - headBytes = hbuf[:] - origIdx = 6 - } + // The complete packet starts from headerSize _before_ the start the body. + // origIdx is the original offset, which will be 0 for a full (12-byte) header or 11 for a minimum (1-byte) header. + headBytes := pkt.header + hSize := headerSizes[pkt.headerType] + origIdx := RTMP_MAX_HEADER_SIZE - hSize - var cSize int + // adjust 1 or 2 bytes for the channel + cSize := 0 switch { case pkt.channel > 319: cSize = 2 @@ -345,12 +342,12 @@ func (pkt *packet) write(s *Session, queue bool) error { cSize = 1 } - hSize := headerSizes[pkt.headerType] if cSize != 0 { origIdx -= cSize hSize += cSize } + // adjust 4 bytes for the timestamp var ts uint32 if prevPkt != nil { ts = uint32(int(pkt.timestamp) - last) @@ -403,8 +400,8 @@ func (pkt *packet) write(s *Session, queue bool) error { } if headerSizes[pkt.headerType] > 8 { - n := int(encodeInt32LE(headBytes[headerIdx:headerIdx+4], pkt.info)) - headerIdx += n + binary.LittleEndian.PutUint32(headBytes[headerIdx:headerIdx+4], uint32(pkt.info)) + headerIdx += 4 // 32bits } if ts >= 0xffffff { @@ -415,31 +412,38 @@ func (pkt *packet) write(s *Session, queue bool) error { size := int(pkt.bodySize) chunkSize := int(s.outChunkSize) - s.log(DebugLevel, pkg+"sending packet", "size", size, "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr()) - - if s.deferred != nil && len(s.deferred)+size+hSize > chunkSize { - err := s.write(s.deferred) - if err != nil { - return err + if s.deferred == nil { + // Defer sending small audio packets (at most once). + if pkt.packetType == RTMP_PACKET_TYPE_AUDIO && size < chunkSize { + s.deferred = headBytes[origIdx:][:size+hSize] + s.log(DebugLevel, pkg+"deferred sending packet", "size", size, "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr()) + return nil + } + } else { + // Send previously deferrd packet if combining it with the next one would exceed the chunk size. + if len(s.deferred)+size+hSize > chunkSize { + s.log(DebugLevel, pkg+"sending deferred packet separately", "size", len(s.deferred)) + err := s.write(s.deferred) + if err != nil { + return err + } + s.deferred = nil } - s.deferred = nil } // TODO(kortschak): Rewrite this horrific peice of premature optimisation. // NB: RTMP wants packets in chunks which are 128 bytes by default, but the server may request a different size. + s.log(DebugLevel, pkg+"sending packet", "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr(), "size", size) for size+hSize != 0 { - if s.deferred == nil && pkt.packetType == RTMP_PACKET_TYPE_AUDIO && size < chunkSize { - s.deferred = headBytes[origIdx:][:size+hSize] - s.log(DebugLevel, pkg+"deferred sending packet") - break - } if chunkSize > size { chunkSize = size } bytes := headBytes[origIdx:][:chunkSize+hSize] if s.deferred != nil { // Prepend the previously deferred packet and write it with the current one. + s.log(DebugLevel, pkg+"combining deferred packet", "size", len(s.deferred)) bytes = append(s.deferred, bytes...) + s.deferred = nil } err := s.write(bytes) if err != nil { @@ -481,9 +485,9 @@ func (pkt *packet) write(s *Session, queue bool) error { if pkt.packetType == RTMP_PACKET_TYPE_INVOKE { buf := pkt.body[1:] meth := C_AMF_DecodeString(buf) + s.log(DebugLevel, pkg+"invoking method "+meth) // keep it in call queue till result arrives if queue { - s.log(DebugLevel, pkg+"queuing method "+meth) buf = buf[3+len(meth):] txn := int32(C_AMF_DecodeNumber(buf[:8])) s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) From 8a23609f6ebb71c1c02f2e64671bc70b59e0f2ec Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 07:24:31 +1030 Subject: [PATCH 29/34] Exit test for errors and fatals errors regardless of testVerbosity. --- rtmp/rtmp_test.go | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 6e07535a..be4cf276 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -6,6 +6,8 @@ DESCRIPTION RTMP tests AUTHORS + Saxon Nelson-Milton + Dan Kortschak Alan Noble LICENSE @@ -49,20 +51,22 @@ const ( testDataDir = "../../test/test-data/av/input" ) -// testVerbosity controls the amount of output +// testVerbosity controls the amount of output. // NB: This is not the log level, which is DebugLevel. // 0: suppress logging completely // 1: log messages only // 2: log messages with errors, if any var testVerbosity = 1 -// testKey is the RTMP key required for YouTube streaming (RTMP_TEST_KEY env var) +// testKey is the YouTube RTMP key required for YouTube streaming (RTMP_TEST_KEY env var). +// NB: don't share your key with others. var testKey string -// testFile is the test video file (RTMP_TEST_FILE env var) +// testFile is the test video file (RTMP_TEST_FILE env var). +// betterInput.h264 is a good one to use. var testFile string -// testLog is a bare bones logger that logs to stdout. +// testLog is a bare bones logger that logs to stdout, and exits upon either an error or fatal error. func testLog(level int8, msg string, params ...interface{}) { logLevels := [...]string{"Debug", "Info", "Warn", "Error", "", "", "Fatal"} if testVerbosity == 0 { @@ -71,21 +75,28 @@ func testLog(level int8, msg string, params ...interface{}) { if level < -1 || level > 5 { panic("Invalid log level") } - if testVerbosity == 2 && len(params) >= 2 { - // extract the params we know about, otherwise just print the message - switch params[0].(string) { - case "error": - fmt.Printf("%s: %s, error=%v\n", logLevels[level+1], msg, params[1].(string)) - case "size": - fmt.Printf("%s: %s, size=%d\n", logLevels[level+1], msg, params[1].(int)) - default: + switch testVerbosity { + case 0: + // silence is golden + case 1: + fmt.Printf("%s: %s\n", logLevels[level+1], msg) + case 2: + // extract the first param if it is one we care about, otherwise just print the message + if len(params) >= 2 { + switch params[0].(string) { + case "error": + fmt.Printf("%s: %s, error=%v\n", logLevels[level+1], msg, params[1].(string)) + case "size": + fmt.Printf("%s: %s, size=%d\n", logLevels[level+1], msg, params[1].(int)) + default: + fmt.Printf("%s: %s\n", logLevels[level+1], msg) + } + } else { fmt.Printf("%s: %s\n", logLevels[level+1], msg) } - } else { - fmt.Printf("%s: %s\n", logLevels[level+1], msg) } - if level == 5 { - // Fatal + if level >= 4 { + // Error or Fatal buf := make([]byte, 1<<16) size := runtime.Stack(buf, true) fmt.Printf("%s\n", string(buf[:size])) From 5be5aad6cf4c587f310572b3ab4fd588c539f241 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 09:52:21 +1030 Subject: [PATCH 30/34] Idiomatic names for constants (1st pass). --- rtmp/packet.go | 90 +++++----- rtmp/rtmp.go | 438 +++++++++++++++++++++++------------------------- rtmp/session.go | 12 +- 3 files changed, 258 insertions(+), 282 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 4fc2141b..3c50b59d 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -39,34 +39,37 @@ import ( "io" ) +// Packet types. const ( - RTMP_PACKET_TYPE_CHUNK_SIZE = 0x01 - RTMP_PACKET_TYPE_BYTES_READ_REPORT = 0x03 - RTMP_PACKET_TYPE_CONTROL = 0x04 - RTMP_PACKET_TYPE_SERVER_BW = 0x05 - RTMP_PACKET_TYPE_CLIENT_BW = 0x06 - RTMP_PACKET_TYPE_AUDIO = 0x08 - RTMP_PACKET_TYPE_VIDEO = 0x09 - RTMP_PACKET_TYPE_FLEX_STREAM_SEND = 0x0F - RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT = 0x10 - RTMP_PACKET_TYPE_FLEX_MESSAGE = 0x11 - RTMP_PACKET_TYPE_INFO = 0x12 - RTMP_PACKET_TYPE_INVOKE = 0x14 - RTMP_PACKET_TYPE_FLASH_VIDEO = 0x16 + packetTypeChunkSize = 0x01 + packetTypeBytesReadReport = 0x03 + packetTypeControl = 0x04 + packetTypeServerBW = 0x05 + packetTypeClientBW = 0x06 + packetTypeAudio = 0x08 + packetTypeVideo = 0x09 + packetTypeFlexStreamSend = 0x0F // not implemented + packetTypeFlexSharedObject = 0x10 // not implemented + packetTypeFlexMessage = 0x11 // not implemented + packetTypeInfo = 0x12 + packetTypeInvoke = 0x14 + packetTypeFlashVideo = 0x16 // not implemented ) +// Header sizes. const ( - RTMP_PACKET_SIZE_LARGE = 0 - RTMP_PACKET_SIZE_MEDIUM = 1 - RTMP_PACKET_SIZE_SMALL = 2 - RTMP_PACKET_SIZE_MINIMUM = 3 - RTMP_PACKET_SIZE_AUTO = 4 + headerSizeLarge = 0 + headerSizeMedium = 1 + headerSizeSmall = 2 + headerSizeMinimum = 3 + headerSizeAuto = 4 ) +// Special channels. const ( - RTMP_CHANNEL_BYTES_READ = 0x02 - RTMP_CHANNEL_CONTROL = 0x03 - RTMP_CHANNEL_SOURCE = 0x04 + chanBytesRead = 0x02 + chanControl = 0x03 + chanSource = 0x04 ) // headerSizes defines header sizes for header types 0, 1, 2 and 3 respectively: @@ -95,13 +98,12 @@ type packet struct { type chunk struct { headerSize int32 data []byte - header [RTMP_MAX_HEADER_SIZE]byte + header [fullHeaderSize]byte } -// ToDo: Consider making the following functions into methods. // read reads a packet. func (pkt *packet) read(s *Session) error { - var hbuf [RTMP_MAX_HEADER_SIZE]byte + var hbuf [fullHeaderSize]byte header := hbuf[:] err := s.read(header[:1]) @@ -161,9 +163,9 @@ func (pkt *packet) read(s *Session) error { size := headerSizes[pkt.headerType] switch { - case size == RTMP_LARGE_HEADER_SIZE: + case size == fullHeaderSize: pkt.hasAbsTimestamp = true - case size < RTMP_LARGE_HEADER_SIZE: + case size < fullHeaderSize: if s.channelsIn[pkt.channel] != nil { *pkt = *(s.channelsIn[pkt.channel]) } @@ -219,6 +221,7 @@ func (pkt *packet) read(s *Session) error { } if pkt.chunk != nil { + panic("non-nil chunk") pkt.chunk.headerSize = int32(hSize) copy(pkt.chunk.header[:], hbuf[:hSize]) pkt.chunk.data = pkt.body[pkt.bytesRead : pkt.bytesRead+uint32(chunkSize)] @@ -260,25 +263,25 @@ func (pkt *packet) read(s *Session) error { // resize adjusts the packet's storage to accommodate a body of the given size. func (pkt *packet) resize(size uint32, ht uint8) { - buf := make([]byte, RTMP_MAX_HEADER_SIZE+size) + buf := make([]byte, fullHeaderSize+size) pkt.header = buf - pkt.body = buf[RTMP_MAX_HEADER_SIZE:] - if ht != RTMP_PACKET_SIZE_AUTO { + pkt.body = buf[fullHeaderSize:] + if ht != headerSizeAuto { pkt.headerType = ht return } switch pkt.packetType { - case RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_AUDIO: + case packetTypeVideo, packetTypeAudio: if pkt.timestamp == 0 { - pkt.headerType = RTMP_PACKET_SIZE_LARGE + pkt.headerType = headerSizeLarge } else { - pkt.headerType = RTMP_PACKET_SIZE_MEDIUM + pkt.headerType = headerSizeMedium } - case RTMP_PACKET_TYPE_INFO: - pkt.headerType = RTMP_PACKET_SIZE_LARGE + case packetTypeInfo: + pkt.headerType = headerSizeLarge pkt.bodySize += 16 default: - pkt.headerType = RTMP_PACKET_SIZE_MEDIUM + pkt.headerType = headerSizeMedium } } @@ -309,14 +312,14 @@ func (pkt *packet) write(s *Session, queue bool) error { prevPkt := s.channelsOut[pkt.channel] var last int - if prevPkt != nil && pkt.headerType != RTMP_PACKET_SIZE_LARGE { + if prevPkt != nil && pkt.headerType != headerSizeLarge { // compress a bit by using the prev packet's attributes - if prevPkt.bodySize == pkt.bodySize && prevPkt.packetType == pkt.packetType && pkt.headerType == RTMP_PACKET_SIZE_MEDIUM { - pkt.headerType = RTMP_PACKET_SIZE_SMALL + if prevPkt.bodySize == pkt.bodySize && prevPkt.packetType == pkt.packetType && pkt.headerType == headerSizeMedium { + pkt.headerType = headerSizeSmall } - if prevPkt.timestamp == pkt.timestamp && pkt.headerType == RTMP_PACKET_SIZE_SMALL { - pkt.headerType = RTMP_PACKET_SIZE_MINIMUM + if prevPkt.timestamp == pkt.timestamp && pkt.headerType == headerSizeSmall { + pkt.headerType = headerSizeMinimum } last = int(prevPkt.timestamp) @@ -331,7 +334,7 @@ func (pkt *packet) write(s *Session, queue bool) error { // origIdx is the original offset, which will be 0 for a full (12-byte) header or 11 for a minimum (1-byte) header. headBytes := pkt.header hSize := headerSizes[pkt.headerType] - origIdx := RTMP_MAX_HEADER_SIZE - hSize + origIdx := fullHeaderSize - hSize // adjust 1 or 2 bytes for the channel cSize := 0 @@ -414,7 +417,7 @@ func (pkt *packet) write(s *Session, queue bool) error { if s.deferred == nil { // Defer sending small audio packets (at most once). - if pkt.packetType == RTMP_PACKET_TYPE_AUDIO && size < chunkSize { + if pkt.packetType == packetTypeAudio && size < chunkSize { s.deferred = headBytes[origIdx:][:size+hSize] s.log(DebugLevel, pkg+"deferred sending packet", "size", size, "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr()) return nil @@ -443,7 +446,6 @@ func (pkt *packet) write(s *Session, queue bool) error { // Prepend the previously deferred packet and write it with the current one. s.log(DebugLevel, pkg+"combining deferred packet", "size", len(s.deferred)) bytes = append(s.deferred, bytes...) - s.deferred = nil } err := s.write(bytes) if err != nil { @@ -482,7 +484,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } // We invoked a remote method - if pkt.packetType == RTMP_PACKET_TYPE_INVOKE { + if pkt.packetType == packetTypeInvoke { buf := pkt.body[1:] meth := C_AMF_DecodeString(buf) s.log(DebugLevel, pkg+"invoking method "+meth) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index e9392019..f48e9e53 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -45,119 +45,94 @@ import ( ) const ( - pkg = "rtmp:" - minDataSize = 11 + pkg = "rtmp:" + minDataSize = 11 // ToDo: this should be the same as fullHeaderSize + signatureSize = 1536 + fullHeaderSize = 12 ) +// Link flags. const ( - RTMPT_OPEN = iota - RTMPT_SEND - RTMPT_IDLE - RTMPT_CLOSE + linkAuth = 0x0001 // using auth param + linkLive = 0x0002 // stream is live + linkSWF = 0x0004 // do SWF verification - not implemented + linkPlaylist = 0x0008 // send playlist before play - not implemented + linkBufx = 0x0010 // toggle stream on BufferEmpty msg - not implemented ) +// Protocol features. const ( - RTMP_READ_HEADER = 0x01 - RTMP_READ_RESUME = 0x02 - RTMP_READ_NO_IGNORE = 0x04 - RTMP_READ_GOTKF = 0x08 - RTMP_READ_GOTFLVK = 0x10 - RTMP_READ_SEEKING = 0x20 - RTMP_READ_COMPLETE = -3 - RTMP_READ_ERROR = -2 - RTMP_READ_EOF = -1 - RTMP_READ_IGNORE = 0 + featureHTTP = 0x01 // not implemented + featureEncode = 0x02 // not implemented + featureSSL = 0x04 // not implemented + featureMFP = 0x08 // not implemented + featureWrite = 0x10 // publish, not play + featureHTTP2 = 0x20 // server-side RTMPT - not implemented ) +// RTMP protocols. const ( - RTMP_LF_AUTH = 0x0001 /* using auth param */ - RTMP_LF_LIVE = 0x0002 /* stream is live */ - RTMP_LF_SWFV = 0x0004 /* do SWF verification */ - RTMP_LF_PLST = 0x0008 /* send playlist before play */ - RTMP_LF_BUFX = 0x0010 /* toggle stream on BufferEmpty msg */ - RTMP_LF_FTCU = 0x0020 /* free tcUrl on close */ - RTMP_LF_FAPU = 0x0040 /* free app on close */ + protoRTMP = 0 + protoRTMPE = featureEncode + protoRTMPT = featureHTTP + protoRTMPS = featureSSL + protoRTMPTE = (featureHTTP | featureEncode) + protoRTMPTS = (featureHTTP | featureSSL) + protoRTMFP = featureMFP ) +// RTMP tokens (lexemes). +// NB: Underscores are deliberately preserved in const names where they exist in the corresponding tokens. const ( - RTMP_FEATURE_HTTP = 0x01 - RTMP_FEATURE_ENC = 0x02 - RTMP_FEATURE_SSL = 0x04 - RTMP_FEATURE_MFP = 0x08 /* not yet supported */ - RTMP_FEATURE_WRITE = 0x10 /* publish, not play */ - RTMP_FEATURE_HTTP2 = 0x20 /* server-side rtmpt */ -) - -const ( - RTMP_PROTOCOL_RTMP = 0 - RTMP_PROTOCOL_RTMPE = RTMP_FEATURE_ENC - RTMP_PROTOCOL_RTMPT = RTMP_FEATURE_HTTP - RTMP_PROTOCOL_RTMPS = RTMP_FEATURE_SSL - RTMP_PROTOCOL_RTMPTE = (RTMP_FEATURE_HTTP | RTMP_FEATURE_ENC) - RTMP_PROTOCOL_RTMPTS = (RTMP_FEATURE_HTTP | RTMP_FEATURE_SSL) - RTMP_PROTOCOL_RTMFP = RTMP_FEATURE_MFP -) - -const ( - RTMP_DEFAULT_CHUNKSIZE = 128 - RTMP_BUFFER_CACHE_SIZE = (16 * 1024) - RTMP_SIG_SIZE = 1536 - RTMP_LARGE_HEADER_SIZE = 12 - RTMP_MAX_HEADER_SIZE = RTMP_LARGE_HEADER_SIZE -) - -const ( - setDataFrame = "@setDataFrame" - - av__checkbw = "_checkbw" - av__onbwcheck = "_onbwcheck" - av__onbwdone = "_onbwdone" - av__result = "_result" - av_app = "app" - av_audioCodecs = "audioCodecs" - av_capabilities = "capabilities" - av_close = "close" - av_code = "code" - av_connect = "connect" - av_createStream = "createStream" - av_deleteStream = "deleteStream" - av_FCPublish = "FCPublish" - av_FCUnpublish = "FCUnpublish" - av_flashVer = "flashVer" - av_fpad = "fpad" - av_level = "level" - av_live = "live" - av_NetConnection_Connect_InvalidApp = "NetConnection.Connect.InvalidApp" - av_NetStream_Failed = "NetStream.Failed" - av_NetStream_Pause_Notify = "NetStream.Pause.Notify" - av_NetStream_Play_Complete = "NetStream.Play.Complete" - av_NetStream_Play_Failed = "NetStream.Play.Failed" - av_NetStream_Play_PublishNotify = "NetStream.Play.PublishNotify" - av_NetStream_Play_Start = "NetStream.Play.Start" - av_NetStream_Play_Stop = "NetStream.Play.Stop" - av_NetStream_Play_StreamNotFound = "NetStream.Play.StreamNotFound" - av_NetStream_Play_UnpublishNotify = "NetStream.Play.UnpublishNotify" - av_NetStream_Publish_Start = "NetStream.Publish.Start" - av_NetStream_Seek_Notify = "NetStream.Seek.Notify" - av_nonprivate = "nonprivate" - av_objectEncoding = "objectEncoding" - av_onBWDone = "onBWDone" - av_onFCSubscribe = "onFCSubscribe" - av_onFCUnsubscribe = "onFCUnsubscribe" - av_onStatus = "onStatus" - av_pageUrl = "pageUrl" - av_ping = "ping" - av_play = "play" - av_playlist_ready = "playlist_ready" - av_publish = "publish" - av_releaseStream = "releaseStream" - av_secureToken = "secureToken" - av_set_playlist = "set_playlist" - av_swfUrl = "swfUrl" - av_tcUrl = "tcUrl" - av_type = "type" - av_videoCodecs = "videoCodecs" - av_videoFunction = "videoFunction" + av_checkbw = "_checkbw" + av_onbwcheck = "_onbwcheck" + av_onbwdone = "_onbwdone" + av_result = "_result" + avApp = "app" + avAudioCodecs = "audioCodecs" + avCapabilities = "capabilities" + avClose = "close" + avCode = "code" + avConnect = "connect" + avCreatestream = "createStream" + avDeletestream = "deleteStream" + avFCPublish = "FCPublish" + avFCUnpublish = "FCUnpublish" + avFlashver = "flashVer" + avFpad = "fpad" + avLevel = "level" + avLive = "live" + avNetConnectionConnectInvalidApp = "NetConnection.Connect.InvalidApp" + avNetStreamFailed = "NetStream.Failed" + avNetStreamPauseNotify = "NetStream.Pause.Notify" + avNetStreamPlayComplete = "NetStream.Play.Complete" + avNetStreamPlayFailed = "NetStream.Play.Failed" + avNetStreamPlayPublishNotify = "NetStream.Play.PublishNotify" + avNetStreamPlayStart = "NetStream.Play.Start" + avNetStreamPlayStop = "NetStream.Play.Stop" + avNetStreamPlayStreamNotFound = "NetStream.Play.StreamNotFound" + avNetStreamPlayUnpublishNotify = "NetStream.Play.UnpublishNotify" + avNetStreamPublish_Start = "NetStream.Publish.Start" + avNetStreamSeekNotify = "NetStream.Seek.Notify" + avNonprivate = "nonprivate" + avObjectEncoding = "objectEncoding" + avOnBWDone = "onBWDone" + avOnFCSubscribe = "onFCSubscribe" + avOnFCUnsubscribe = "onFCUnsubscribe" + avOnStatus = "onStatus" + avPageUrl = "pageUrl" + avPing = "ping" + avPlay = "play" + avPlaylist_ready = "playlist_ready" + avPublish = "publish" + avReleasestream = "releaseStream" + avSecureToken = "secureToken" + avSet_playlist = "set_playlist" + avSwfUrl = "swfUrl" + avTcUrl = "tcUrl" + avType = "type" + avVideoCodecs = "videoCodecs" + avVideoFunction = "videoFunction" ) // RTMP protocol strings. @@ -200,7 +175,6 @@ func setupURL(s *Session) (err error) { if s.link.tcUrl == "" { if s.link.app != "" { s.link.tcUrl = rtmpProtocolStrings[s.link.protocol] + "://" + s.link.host + ":" + strconv.Itoa(int(s.link.port)) + "/" + s.link.app - s.link.lFlags |= RTMP_LF_FTCU } else { s.link.tcUrl = s.url } @@ -208,10 +182,10 @@ func setupURL(s *Session) (err error) { if s.link.port == 0 { switch { - case (s.link.protocol & RTMP_FEATURE_SSL) != 0: + case (s.link.protocol & featureSSL) != 0: s.link.port = 433 s.log(FatalLevel, pkg+"SSL not supported") - case (s.link.protocol & RTMP_FEATURE_HTTP) != 0: + case (s.link.protocol & featureHTTP) != 0: s.link.port = 80 default: s.link.port = 1935 @@ -257,7 +231,7 @@ func connectStream(s *Session) error { } switch pkt.packetType { - case RTMP_PACKET_TYPE_AUDIO, RTMP_PACKET_TYPE_VIDEO, RTMP_PACKET_TYPE_INFO: + case packetTypeAudio, packetTypeVideo, packetTypeInfo: s.log(WarnLevel, pkg+"got packet before play; ignoring", "type", pkt.packetType) default: err = handlePacket(s, &pkt) @@ -277,21 +251,21 @@ func connectStream(s *Session) error { // NB: cases have been commented out that are not currently used by AusOcean func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { - case RTMP_PACKET_TYPE_CHUNK_SIZE: + case packetTypeChunkSize: if pkt.bodySize >= 4 { s.inChunkSize = int32(C_AMF_DecodeInt32(pkt.body[:4])) } - case RTMP_PACKET_TYPE_BYTES_READ_REPORT: + case packetTypeBytesReadReport: s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) - case RTMP_PACKET_TYPE_CONTROL: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_CONTROL") + case packetTypeControl: + s.log(FatalLevel, pkg+"unsupported packet type packetTypeControl") - case RTMP_PACKET_TYPE_SERVER_BW: + case packetTypeServerBW: s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) - case RTMP_PACKET_TYPE_CLIENT_BW: + case packetTypeClientBW: s.clientBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) if pkt.bodySize > 4 { s.clientBW2 = pkt.body[4] @@ -299,19 +273,19 @@ func handlePacket(s *Session, pkt *packet) error { s.clientBW2 = 0xff } - case RTMP_PACKET_TYPE_AUDIO: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_AUDIO") + case packetTypeAudio: + s.log(FatalLevel, pkg+"unsupported packet type packetTypeAudio") - case RTMP_PACKET_TYPE_VIDEO: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_VIDEO") + case packetTypeVideo: + s.log(FatalLevel, pkg+"unsupported packet type packetTypeVideo") - case RTMP_PACKET_TYPE_FLEX_MESSAGE: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_FLEX_MESSAGE") + case packetTypeFlexMessage: + s.log(FatalLevel, pkg+"unsupported packet type packetTypeFlexMessage") - case RTMP_PACKET_TYPE_INFO: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_INFO") + case packetTypeInfo: + s.log(FatalLevel, pkg+"unsupported packet type packetTypeInfo") - case RTMP_PACKET_TYPE_INVOKE: + case packetTypeInvoke: err := handleInvoke(s, pkt.body[:pkt.bodySize]) if err != nil { // This will never happen with the methods we implement. @@ -319,8 +293,8 @@ func handlePacket(s *Session, pkt *packet) error { return err } - case RTMP_PACKET_TYPE_FLASH_VIDEO: - s.log(FatalLevel, pkg+"unsupported packet type RTMP_PACKET_TYPE_FLASH_VIDEO") + case packetTypeFlashVideo: + s.log(FatalLevel, pkg+"unsupported packet type packetType_FLASHVideo") default: s.log(WarnLevel, pkg+"unknown packet type", "type", pkt.packetType) @@ -331,15 +305,15 @@ func handlePacket(s *Session, pkt *packet) error { func sendConnectPacket(s *Session) error { var pbuf [4096]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_LARGE, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeLarge, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_connect) + enc = C_AMF_EncodeString(enc, avConnect) if enc == nil { return errEncoding } @@ -351,60 +325,60 @@ func sendConnectPacket(s *Session) error { enc[0] = AMF_OBJECT enc = enc[1:] - enc = C_AMF_EncodeNamedString(enc, av_app, s.link.app) + enc = C_AMF_EncodeNamedString(enc, avApp, s.link.app) if enc == nil { return errEncoding } - if s.link.protocol&RTMP_FEATURE_WRITE != 0 { - enc = C_AMF_EncodeNamedString(enc, av_type, av_nonprivate) + if s.link.protocol&featureWrite != 0 { + enc = C_AMF_EncodeNamedString(enc, avType, avNonprivate) if enc == nil { return errEncoding } } if s.link.flashVer != "" { - enc = C_AMF_EncodeNamedString(enc, av_flashVer, s.link.flashVer) + enc = C_AMF_EncodeNamedString(enc, avFlashver, s.link.flashVer) if enc == nil { return errEncoding } } if s.link.swfUrl != "" { - enc = C_AMF_EncodeNamedString(enc, av_swfUrl, s.link.swfUrl) + enc = C_AMF_EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) if enc == nil { return errEncoding } } if s.link.tcUrl != "" { - enc = C_AMF_EncodeNamedString(enc, av_tcUrl, s.link.tcUrl) + enc = C_AMF_EncodeNamedString(enc, avTcUrl, s.link.tcUrl) if enc == nil { return errEncoding } } - if s.link.protocol&RTMP_FEATURE_WRITE == 0 { - enc = C_AMF_EncodeNamedBoolean(enc, av_fpad, false) + if s.link.protocol&featureWrite == 0 { + enc = C_AMF_EncodeNamedBoolean(enc, avFpad, false) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, av_capabilities, 15) + enc = C_AMF_EncodeNamedNumber(enc, avCapabilities, 15) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, av_audioCodecs, s.audioCodecs) + enc = C_AMF_EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, av_videoCodecs, s.videoCodecs) + enc = C_AMF_EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, av_videoFunction, 1) + enc = C_AMF_EncodeNamedNumber(enc, avVideoFunction, 1) if enc == nil { return errEncoding } if s.link.pageUrl != "" { - enc = C_AMF_EncodeNamedString(enc, av_pageUrl, s.link.pageUrl) + enc = C_AMF_EncodeNamedString(enc, avPageUrl, s.link.pageUrl) if enc == nil { return errEncoding } @@ -412,7 +386,7 @@ func sendConnectPacket(s *Session) error { } if s.encoding != 0.0 || s.sendEncoding { - enc = C_AMF_EncodeNamedNumber(enc, av_objectEncoding, s.encoding) + enc = C_AMF_EncodeNamedNumber(enc, avObjectEncoding, s.encoding) if enc == nil { return errEncoding } @@ -423,7 +397,7 @@ func sendConnectPacket(s *Session) error { // add auth string if s.link.auth != "" { - enc = C_AMF_EncodeBoolean(enc, s.link.lFlags&RTMP_LF_AUTH != 0) + enc = C_AMF_EncodeBoolean(enc, s.link.flags&linkAuth != 0) if enc == nil { return errEncoding } @@ -440,7 +414,7 @@ func sendConnectPacket(s *Session) error { } } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, true) // response expected } @@ -448,15 +422,15 @@ func sendConnectPacket(s *Session) error { func sendCreateStream(s *Session) error { var pbuf [256]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeMedium, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_createStream) + enc = C_AMF_EncodeString(enc, avCreatestream) if enc == nil { return errEncoding } @@ -468,7 +442,7 @@ func sendCreateStream(s *Session) error { enc[0] = AMF_NULL enc = enc[1:] - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, true) // response expected } @@ -476,15 +450,15 @@ func sendCreateStream(s *Session) error { func sendReleaseStream(s *Session) error { var pbuf [1024]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeMedium, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_releaseStream) + enc = C_AMF_EncodeString(enc, avReleasestream) if enc == nil { return errEncoding } @@ -499,7 +473,7 @@ func sendReleaseStream(s *Session) error { if enc == nil { return errEncoding } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, false) } @@ -507,15 +481,15 @@ func sendReleaseStream(s *Session) error { func sendFCPublish(s *Session) error { var pbuf [1024]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeMedium, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_FCPublish) + enc = C_AMF_EncodeString(enc, avFCPublish) if enc == nil { return errEncoding } @@ -531,7 +505,7 @@ func sendFCPublish(s *Session) error { return errEncoding } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, false) } @@ -539,15 +513,15 @@ func sendFCPublish(s *Session) error { func sendFCUnpublish(s *Session) error { var pbuf [1024]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeMedium, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_FCUnpublish) + enc = C_AMF_EncodeString(enc, avFCUnpublish) if enc == nil { return errEncoding } @@ -563,7 +537,7 @@ func sendFCUnpublish(s *Session) error { return errEncoding } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, false) } @@ -571,15 +545,15 @@ func sendFCUnpublish(s *Session) error { func sendPublish(s *Session) error { var pbuf [1024]byte pkt := packet{ - channel: RTMP_CHANNEL_SOURCE, - headerType: RTMP_PACKET_SIZE_LARGE, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanSource, + headerType: headerSizeLarge, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_publish) + enc = C_AMF_EncodeString(enc, avPublish) if enc == nil { return errEncoding } @@ -594,12 +568,12 @@ func sendPublish(s *Session) error { if enc == nil { return errEncoding } - enc = C_AMF_EncodeString(enc, av_live) + enc = C_AMF_EncodeString(enc, avLive) if enc == nil { return errEncoding } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, true) // response expected } @@ -607,15 +581,15 @@ func sendPublish(s *Session) error { func sendDeleteStream(s *Session, dStreamId float64) error { var pbuf [256]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeMedium, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_deleteStream) + enc = C_AMF_EncodeString(enc, avDeletestream) if enc == nil { return errEncoding } @@ -630,7 +604,7 @@ func sendDeleteStream(s *Session, dStreamId float64) error { if enc == nil { return errEncoding } - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) /* no response expected */ return pkt.write(s, false) @@ -640,11 +614,11 @@ func sendDeleteStream(s *Session, dStreamId float64) error { func sendBytesReceived(s *Session) error { var pbuf [256]byte pkt := packet{ - channel: RTMP_CHANNEL_BYTES_READ, - headerType: RTMP_PACKET_SIZE_MEDIUM, - packetType: RTMP_PACKET_TYPE_BYTES_READ_REPORT, + channel: chanBytesRead, + headerType: headerSizeMedium, + packetType: packetTypeBytesReadReport, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body @@ -661,15 +635,15 @@ func sendBytesReceived(s *Session) error { func sendCheckBW(s *Session) error { var pbuf [256]byte pkt := packet{ - channel: RTMP_CHANNEL_CONTROL, - headerType: RTMP_PACKET_SIZE_LARGE, - packetType: RTMP_PACKET_TYPE_INVOKE, + channel: chanControl, + headerType: headerSizeLarge, + packetType: packetTypeInvoke, header: pbuf[:], - body: pbuf[RTMP_MAX_HEADER_SIZE:], + body: pbuf[fullHeaderSize:], } enc := pkt.body - enc = C_AMF_EncodeString(enc, av__checkbw) + enc = C_AMF_EncodeString(enc, av_checkbw) if enc == nil { return errEncoding } @@ -681,7 +655,7 @@ func sendCheckBW(s *Session) error { enc[0] = AMF_NULL enc = enc[1:] - pkt.bodySize = uint32((len(pbuf) - RTMP_MAX_HEADER_SIZE) - len(enc)) + pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, false) } @@ -693,7 +667,7 @@ func eraseMethod(m []method, i int) []method { } // int handleInvoke handles a packet invoke request -// Side effects: s.isPlaying set to true upon av_NetStream_Publish_Start +// Side effects: s.isPlaying set to true upon avNetStreamPublish_Start func handleInvoke(s *Session, body []byte) error { if body[0] != 0x02 { return errInvalidBody @@ -709,7 +683,7 @@ func handleInvoke(s *Session, body []byte) error { txn := C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 1)) switch meth { - case av__result: + case av_result: var methodInvoked string for i, m := range s.methodCalls { if float64(m.num) == txn { @@ -725,11 +699,11 @@ func handleInvoke(s *Session, body []byte) error { s.log(DebugLevel, pkg+"received result for "+methodInvoked) switch methodInvoked { - case av_connect: + case avConnect: if s.link.token != "" { s.log(FatalLevel, pkg+"no support for link token") } - if (s.link.protocol & RTMP_FEATURE_WRITE) == 0 { + if (s.link.protocol & featureWrite) == 0 { return errNotWritable } err := sendReleaseStream(s) @@ -745,9 +719,9 @@ func handleInvoke(s *Session, body []byte) error { return err } - case av_createStream: + case avCreatestream: s.streamID = int32(C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 3))) - if s.link.protocol&RTMP_FEATURE_WRITE == 0 { + if s.link.protocol&featureWrite == 0 { return errNotWritable } err := sendPublish(s) @@ -755,11 +729,11 @@ func handleInvoke(s *Session, body []byte) error { return err } - case av_play, av_publish: - s.log(FatalLevel, pkg+"unsupported method av_play/av_publish") + case avPlay, avPublish: + s.log(FatalLevel, pkg+"unsupported method avPlay/avPublish") } - case av_onBWDone: + case avOnBWDone: if s.checkCounter == 0 { // ToDo: why is this always zero? err := sendCheckBW(s) if err != nil { @@ -767,59 +741,59 @@ func handleInvoke(s *Session, body []byte) error { } } - case av_onFCUnsubscribe, av_onFCSubscribe: - s.log(FatalLevel, pkg+"unsupported method av_onFCUnsubscribe/av_onFCSubscribe") + case avOnFCUnsubscribe, avOnFCSubscribe: + s.log(FatalLevel, pkg+"unsupported method avOnFCUnsubscribe/avOonfcsubscribe") - case av_ping: - s.log(FatalLevel, pkg+"unsupported method av_ping") + case avPing: + s.log(FatalLevel, pkg+"unsupported method avPing") - case av__onbwcheck: + case av_onbwcheck: s.log(FatalLevel, pkg+"unsupported method av_onbwcheck") - case av__onbwdone: + case av_onbwdone: s.log(FatalLevel, pkg+"unsupported method av_onbwdone") - case av_close: - s.log(FatalLevel, pkg+"unsupported method av_close") + case avClose: + s.log(FatalLevel, pkg+"unsupported method avClose") - case av_onStatus: + case avOnStatus: var obj2 C_AMFObject C_AMFProp_GetObject(C_AMF_GetProp(&obj, "", 3), &obj2) - code := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_code, -1)) - level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, av_level, -1)) + code := C_AMFProp_GetString(C_AMF_GetProp(&obj2, avCode, -1)) + level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, avLevel, -1)) s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { - case av_NetStream_Failed, av_NetStream_Play_Failed, - av_NetStream_Play_StreamNotFound, av_NetConnection_Connect_InvalidApp: - s.log(FatalLevel, pkg+"unsupported method av_NetStream/av_NetStream_Play_Failed/av_netSTream_Play_StreamNotFound/av_netConnection_Connect_invalidApp") + case avNetStreamFailed, avNetStreamPlayFailed, + avNetStreamPlayStreamNotFound, avNetConnectionConnectInvalidApp: + s.log(FatalLevel, pkg+"unsupported method avNetStream/avNetStreamPlayFailed/avNetstream_play_streamnotfound/av_netConnection_Connect_invalidApp") - case av_NetStream_Play_Start, av_NetStream_Play_PublishNotify: - s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Start/av_NetStream_Play_PublishNotify") + case avNetStreamPlayStart, avNetStreamPlayPublishNotify: + s.log(FatalLevel, pkg+"unsupported method avNetStreamPlayStart/avNetStreamPlayPublishNotify") - case av_NetStream_Publish_Start: + case avNetStreamPublish_Start: s.log(DebugLevel, pkg+"playing") s.isPlaying = true for i, m := range s.methodCalls { - if m.name == av_publish { + if m.name == avPublish { s.methodCalls = eraseMethod(s.methodCalls, i) break } } - // ToDo: handle case when av_publish method not found + // ToDo: handle case when avPublish method not found - case av_NetStream_Play_Complete, av_NetStream_Play_Stop, av_NetStream_Play_UnpublishNotify: - s.log(FatalLevel, pkg+"unsupported method av_NetStream_Play_Complete/av_NetStream_Play_Stop/av_NetStream_Play_UnpublishNotify") + case avNetStreamPlayComplete, avNetStreamPlayStop, avNetStreamPlayUnpublishNotify: + s.log(FatalLevel, pkg+"unsupported method avNetStreamPlayComplete/avNetStreamPlayStop/avNetStreamPlayUnpublishNotify") - case av_NetStream_Seek_Notify: - s.log(FatalLevel, pkg+"unsupported method av_netStream_Seek_Notify") + case avNetStreamSeekNotify: + s.log(FatalLevel, pkg+"unsupported method avNetstream_seek_notify") - case av_NetStream_Pause_Notify: - s.log(FatalLevel, pkg+"unsupported method av_NetStream_Pause_Notify") + case avNetStreamPauseNotify: + s.log(FatalLevel, pkg+"unsupported method avNetStreamPauseNotify") } - case av_playlist_ready: - s.log(FatalLevel, pkg+"unsupported method av_playlist_ready") + case avPlaylist_ready: + s.log(FatalLevel, pkg+"unsupported method avPlaylist_ready") default: s.log(FatalLevel, pkg+"unknown method "+meth) @@ -830,15 +804,15 @@ leave: } func handshake(s *Session) error { - var clientbuf [RTMP_SIG_SIZE + 1]byte + var clientbuf [signatureSize + 1]byte clientsig := clientbuf[1:] - var serversig [RTMP_SIG_SIZE]byte - clientbuf[0] = RTMP_CHANNEL_CONTROL + var serversig [signatureSize]byte + clientbuf[0] = chanControl binary.BigEndian.PutUint32(clientsig, uint32(time.Now().UnixNano()/1000000)) copy(clientsig[4:8], []byte{0, 0, 0, 0}) - for i := 8; i < RTMP_SIG_SIZE; i++ { + for i := 8; i < signatureSize; i++ { clientsig[i] = byte(rand.Intn(256)) } @@ -878,8 +852,8 @@ func handshake(s *Session) error { return err } - if !bytes.Equal(serversig[:RTMP_SIG_SIZE], clientbuf[1:RTMP_SIG_SIZE+1]) { - s.log(WarnLevel, pkg+"signature mismatch", "serversig", serversig[:RTMP_SIG_SIZE], "clientsig", clientbuf[1:RTMP_SIG_SIZE+1]) + if !bytes.Equal(serversig[:signatureSize], clientbuf[1:signatureSize+1]) { + s.log(WarnLevel, pkg+"signature mismatch", "serversig", serversig[:signatureSize], "clientsig", clientbuf[1:signatureSize+1]) } return nil } diff --git a/rtmp/session.go b/rtmp/session.go index aa1ee80b..aa4c0ac2 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -80,7 +80,7 @@ type link struct { flashVer string token string extras C_AMFObject - lFlags int32 + flags int32 swfAge int32 protocol int32 timeout uint @@ -159,7 +159,7 @@ func (s *Session) Close() error { return errNotConnected } if s.streamID > 0 { - if s.link.protocol&RTMP_FEATURE_WRITE != 0 { + if s.link.protocol&featureWrite != 0 { sendFCUnpublish(s) } sendDeleteStream(s, float64(s.streamID)) @@ -174,7 +174,7 @@ func (s *Session) Write(data []byte) (int, error) { if !s.isConnected() { return 0, errNotConnected } - if data[0] == RTMP_PACKET_TYPE_INFO || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { + if data[0] == packetTypeInfo || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { return 0, errUnimplemented } if len(data) < minDataSize { @@ -185,11 +185,11 @@ func (s *Session) Write(data []byte) (int, error) { packetType: data[0], bodySize: C_AMF_DecodeInt24(data[1:4]), timestamp: C_AMF_DecodeInt24(data[4:7]) | uint32(data[7])<<24, - channel: RTMP_CHANNEL_SOURCE, + channel: chanSource, info: s.streamID, } - pkt.resize(pkt.bodySize, RTMP_PACKET_SIZE_AUTO) + pkt.resize(pkt.bodySize, headerSizeAuto) copy(pkt.body, data[minDataSize:minDataSize+pkt.bodySize]) err := pkt.write(s, false) if err != nil { @@ -245,5 +245,5 @@ func (s *Session) isConnected() bool { // enableWrite enables the current session for writing. func (s *Session) enableWrite() { - s.link.protocol |= RTMP_FEATURE_WRITE + s.link.protocol |= featureWrite } From fc815c23e742ff6c83620960f2a638ad78c8a0ec Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 10:10:27 +1030 Subject: [PATCH 31/34] Session.Write() now checks len(data) before the FLV test. --- rtmp/session.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtmp/session.go b/rtmp/session.go index aa4c0ac2..8ba30d1c 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -152,7 +152,7 @@ func (s *Session) Open() error { } // Close terminates the rtmp connection. -// NB: The session object is cleared completely. +// NB: Close is idempotent and the session value is cleared completely. func (s *Session) Close() error { s.log(DebugLevel, pkg+"Session.Close") if !s.isConnected() { @@ -174,12 +174,12 @@ func (s *Session) Write(data []byte) (int, error) { if !s.isConnected() { return 0, errNotConnected } - if data[0] == packetTypeInfo || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { - return 0, errUnimplemented - } if len(data) < minDataSize { return 0, errTinyPacket } + if data[0] == packetTypeInfo || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { + return 0, errUnimplemented + } pkt := packet{ packetType: data[0], From 67cc591dd249364facff3b3b01eba49d10436aec Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 10:27:41 +1030 Subject: [PATCH 32/34] Use idiomatic consts. --- rtmp/parseurl.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/rtmp/parseurl.go b/rtmp/parseurl.go index 97110eb8..eae4277e 100644 --- a/rtmp/parseurl.go +++ b/rtmp/parseurl.go @@ -8,9 +8,10 @@ DESCRIPTION AUTHOR Dan Kortschak Saxon Nelson-Milton + Alan Noble LICENSE - parseurl.go is Copyright (C) 2017-2018 the Australian Ocean Lab (AusOcean) + parseurl.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the @@ -33,7 +34,6 @@ LICENSE package rtmp import ( - "log" "net/url" "path" "strconv" @@ -41,30 +41,29 @@ import ( ) // parseURL parses an RTMP URL (ok, technically it is lexing). +// func parseURL(addr string) (protocol int32, host string, port uint16, app, playpath string, err error) { u, err := url.Parse(addr) if err != nil { - log.Printf("failed to parse addr: %v", err) return protocol, host, port, app, playpath, err } switch u.Scheme { case "rtmp": - protocol = RTMP_PROTOCOL_RTMP + protocol = protoRTMP case "rtmpt": - protocol = RTMP_PROTOCOL_RTMPT + protocol = protoRTMPT case "rtmps": - protocol = RTMP_PROTOCOL_RTMPS + protocol = protoRTMPS case "rtmpe": - protocol = RTMP_PROTOCOL_RTMPE + protocol = protoRTMPE case "rtmfp": - protocol = RTMP_PROTOCOL_RTMFP + protocol = protoRTMFP case "rtmpte": - protocol = RTMP_PROTOCOL_RTMPTE + protocol = protoRTMPTE case "rtmpts": - protocol = RTMP_PROTOCOL_RTMPTS + protocol = protoRTMPTS default: - log.Printf("unknown scheme: %q", u.Scheme) return protocol, host, port, app, playpath, errUnknownScheme } From 22b76b5bda7d95c73c097f03d61749a23cc20047 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 10:35:20 +1030 Subject: [PATCH 33/34] Session.read()/write() both now return (int, error). --- rtmp/packet.go | 19 ++++++++++--------- rtmp/rtmp.go | 11 +++++------ rtmp/session.go | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 3c50b59d..dac3d803 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -106,7 +106,7 @@ func (pkt *packet) read(s *Session) error { var hbuf [fullHeaderSize]byte header := hbuf[:] - err := s.read(header[:1]) + _, err := s.read(header[:1]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 1st byte", "error", err.Error()) if err == io.EOF { @@ -120,7 +120,7 @@ func (pkt *packet) read(s *Session) error { switch { case pkt.channel == 0: - err = s.read(header[:1]) + _, err = s.read(header[:1]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 2nd byte", "error", err.Error()) return err @@ -129,7 +129,7 @@ func (pkt *packet) read(s *Session) error { pkt.channel = int32(header[0]) + 64 case pkt.channel == 1: - err = s.read(header[:2]) + _, err = s.read(header[:2]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header 3rd byte", "error", err.Error()) return err @@ -173,7 +173,7 @@ func (pkt *packet) read(s *Session) error { size-- if size > 0 { - err = s.read(header[:size]) + _, err = s.read(header[:size]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet header", "error", err.Error()) return err @@ -199,7 +199,7 @@ func (pkt *packet) read(s *Session) error { extendedTimestamp := pkt.timestamp == 0xffffff if extendedTimestamp { - err = s.read(header[size : size+4]) + _, err = s.read(header[size : size+4]) if err != nil { s.log(DebugLevel, pkg+"failed to read extended timestamp", "error", err.Error()) return err @@ -227,7 +227,7 @@ func (pkt *packet) read(s *Session) error { pkt.chunk.data = pkt.body[pkt.bytesRead : pkt.bytesRead+uint32(chunkSize)] } - err = s.read(pkt.body[pkt.bytesRead:][:chunkSize]) + _, err = s.read(pkt.body[pkt.bytesRead:][:chunkSize]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet body", "error", err.Error()) return err @@ -261,7 +261,7 @@ func (pkt *packet) read(s *Session) error { return nil } -// resize adjusts the packet's storage to accommodate a body of the given size. +// resize adjusts the packet's storage to accommodate a body of the given size and header type. func (pkt *packet) resize(size uint32, ht uint8) { buf := make([]byte, fullHeaderSize+size) pkt.header = buf @@ -286,6 +286,7 @@ func (pkt *packet) resize(size uint32, ht uint8) { } // write sends a packet. +// When queue is true, we expect a response to this request and cache the method on s.methodCalls. func (pkt *packet) write(s *Session, queue bool) error { if pkt.body == nil { return errInvalidBody @@ -426,7 +427,7 @@ func (pkt *packet) write(s *Session, queue bool) error { // Send previously deferrd packet if combining it with the next one would exceed the chunk size. if len(s.deferred)+size+hSize > chunkSize { s.log(DebugLevel, pkg+"sending deferred packet separately", "size", len(s.deferred)) - err := s.write(s.deferred) + _, err := s.write(s.deferred) if err != nil { return err } @@ -447,7 +448,7 @@ func (pkt *packet) write(s *Session, queue bool) error { s.log(DebugLevel, pkg+"combining deferred packet", "size", len(s.deferred)) bytes = append(s.deferred, bytes...) } - err := s.write(bytes) + _, err := s.write(bytes) if err != nil { return err } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index f48e9e53..4b334587 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -606,7 +606,6 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - /* no response expected */ return pkt.write(s, false) } @@ -816,14 +815,14 @@ func handshake(s *Session) error { clientsig[i] = byte(rand.Intn(256)) } - err := s.write(clientbuf[:]) + _, err := s.write(clientbuf[:]) if err != nil { return err } s.log(DebugLevel, pkg+"handshake sent") var typ [1]byte - err = s.read(typ[:]) + _, err = s.read(typ[:]) if err != nil { return err } @@ -832,7 +831,7 @@ func handshake(s *Session) error { if typ[0] != clientbuf[0] { s.log(WarnLevel, pkg+"handshake type mismatch", "sent", clientbuf[0], "received", typ) } - err = s.read(serversig[:]) + _, err = s.read(serversig[:]) if err != nil { return err } @@ -842,12 +841,12 @@ func handshake(s *Session) error { s.log(DebugLevel, pkg+"server uptime", "uptime", suptime) // 2nd part of handshake - err = s.write(serversig[:]) + _, err = s.write(serversig[:]) if err != nil { return err } - err = s.read(serversig[:]) + _, err = s.read(serversig[:]) if err != nil { return err } diff --git a/rtmp/session.go b/rtmp/session.go index 8ba30d1c..eb97c938 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -203,39 +203,39 @@ func (s *Session) Write(data []byte) (int, error) { // read from an RTMP connection. Sends a bytes received message if the // number of bytes received (nBytesIn) is greater than the number sent // (nBytesInSent) by 10% of the bandwidth. -func (s *Session) read(buf []byte) error { +func (s *Session) read(buf []byte) (int, error) { err := s.link.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) if err != nil { - return err + return 0, err } n, err := io.ReadFull(s.link.conn, buf) if err != nil { s.log(DebugLevel, pkg+"read failed", "error", err.Error()) - return err + return 0, err } s.nBytesIn += int32(n) if s.nBytesIn > (s.nBytesInSent + s.clientBW/10) { err := sendBytesReceived(s) if err != nil { - return err + return n, err // NB: we still read n bytes, even though send bytes failed } } - return nil + return n, nil } // write to an RTMP connection. -func (s *Session) write(buf []byte) error { +func (s *Session) write(buf []byte) (int, error) { //ToDo: consider using a different timeout for writes than for reads err := s.link.conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(s.link.timeout))) if err != nil { - return err + return 0, err } - _, err = s.link.conn.Write(buf) + n, err := s.link.conn.Write(buf) if err != nil { s.log(WarnLevel, pkg+"write failed", "error", err.Error()) - return err + return 0, err } - return nil + return n, nil } // isConnected returns true if the RTMP connection is up. From 2f3c2cc0e2d1c0859ed81031983508c159b9a77f Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 11:17:14 +1030 Subject: [PATCH 34/34] Added ToDo. --- rtmp/rtmp_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index be4cf276..01dad10f 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -177,13 +177,16 @@ func TestFromFrame(t *testing.T) { } // Pass RTMP session, true for audio, true for video, and 25 FPS + // ToDo: fix this. Although we can encode the file and YouTube + // doesn't complain, YouTube doesn't play it (even when we + // send 1 minute's worth). flvEncoder := flv.NewEncoder(s, true, true, 25) for i := 0; i < 25; i++ { err := flvEncoder.Encode(b) if err != nil { t.Errorf("Encoding failed with error: %v", err) } - time.Sleep(40 * time.Millisecond) // rate limit to 1/25s + time.Sleep(time.Millisecond / 25) // rate limit to 1/25s } err = s.Close()