commit messages:

* rtmp.ServerSession 使用channel发送数据,增加Dispose方法
* rtmp.Group 缓存avc header,aac header
pull/200/head
q191201771 6 years ago
parent 3630b7568d
commit 79ebdbe8c8

@ -19,11 +19,7 @@ type GroupManager struct {
exitChan chan bool
rtmpGroup *rtmp.Group
httpFlvGroup *httpflv.Group
//rtmpPullSession *rtmp.PullSession
//httpFlvPullSession *httpflv.PullSession
//turnToEmptyTick int64 // trace while sub session list turn to empty
//gopCache *httpflv.GOPCache
mutex sync.Mutex
mutex sync.Mutex
UniqueKey string
}
@ -37,9 +33,7 @@ func NewGroupManager(appName string, streamName string, config *Config) *GroupMa
appName: appName,
streamName: streamName,
exitChan: make(chan bool),
//httpFlvSubSessionList: make(map[*httpflv.SubSession]struct{}),
//gopCache: httpflv.NewGOPCache(config.GOPCacheNum),
UniqueKey: uk,
UniqueKey: uk,
}
}
@ -52,39 +46,7 @@ func (gm *GroupManager) RunLoop() {
case <-gm.exitChan:
return
case <-t.C:
//now := time.Now().Unix()
// TODO chef: do timeout stuff. and do it fast.
//group.mutex.Lock()
//if group.httpFlvPullSession != nil {
// if isReadTimeout, _ := group.httpFlvPullSession.ConnStat.Check(now); isReadTimeout {
// log.Warnf("pull session read timeout. [%s]", group.httpFlvPullSession.UniqueKey)
// group.disposePullSession(lalErr)
// }
//}
//group.mutex.Unlock()
//group.mutex.Lock()
//for session := range group.httpFlvSubSessionList {
// if _, isWriteTimeout := session.ConnStat.Check(now); isWriteTimeout {
// log.Warnf("sub session write timeout. [%s]", session)
// delete(group.httpFlvSubSessionList, session)
// session.Dispose(lalErr)
// }
//}
//group.mutex.Unlock()
//if group.config.Pull.StopPullWhileNoSubTimeout != 0 {
// group.mutex.Lock()
// if group.httpFlvPullSession != nil && group.turnToEmptyTick != 0 && len(group.httpFlvSubSessionList) == 0 &&
// now-group.turnToEmptyTick > group.config.Pull.StopPullWhileNoSubTimeout {
//
// log.Infof("stop pull while no SubSession. [%s]", group.httpFlvPullSession.UniqueKey)
// group.disposePullSession(lalErr)
// }
// group.mutex.Unlock()
//}
// noop
}
}
}
@ -139,7 +101,6 @@ func (gm *GroupManager) ReadFlvTagCB(tag *httpflv.Tag) {
// GroupObserver of rtmp.Group
func (gm *GroupManager) ReadRTMPAVMsgCB(header rtmp.Header, timestampAbs int, message []byte) {
log.Info("ReadRTMPAVMsgCB")
// TODO chef: broadcast to httpflv.Group
}

@ -69,7 +69,9 @@ func loadConf(confFile string) *Config {
func startWebPProf() {
if err := http.ListenAndServe("0.0.0.0:10001", nil); err != nil {
log.Error(err)
return
}
log.Info("start pprof listen. addr=:10001")
}
func shutdownAfter(d time.Duration) {

@ -8,7 +8,7 @@ fi
cd app/lal && \
go build -ldflags " \
-X 'github.com/q191201771/lal/pkg/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
-X 'github.com/q191201771/lal/pkg/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
-X 'github.com/q191201771/lal/pkg/bininfo.GoVersion=`go version`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.BuildGoVersion=`go version`' \
" -o ../../bin/lal

@ -8,7 +8,7 @@ fi
cd app/lal && \
GOOS=linux GOARCH=amd64 go build -ldflags " \
-X 'github.com/q191201771/lal/pkg/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
-X 'github.com/q191201771/lal/pkg/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
-X 'github.com/q191201771/lal/pkg/bininfo.GoVersion=`go version`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
-X 'github.com/q191201771/lal/pkg/util/bininfo.BuildGoVersion=`go version`' \
" -o ../../bin/lal_linux

@ -0,0 +1,45 @@
package avc
// H.264-AVC-ISO_IEC_14496-15.pdf
// 5.2.4 Decoder configuration information
// <buf> body of tag
//func parseAVCSeqHeader(buf []byte) (sps, pps []byte, err error) {
// // TODO chef: check if read out of <buf> range
//
// if buf[0] != AVCKey || buf[1] != isAVCKeySeqHeader || buf[2] != 0 || buf[3] != 0 || buf[4] != 0 {
// log.Error("parse avc seq header failed.")
// err = httpFlvErr
// return
// }
//
// //configurationVersion := buf[5]
// //avcProfileIndication := buf[6]
// //profileCompatibility := buf[7]
// //avcLevelIndication := buf[8]
// //lengthSizeMinusOne := buf[9] & 0x03
//
// index := 10
//
// numOfSPS := int(buf[index] & 0x1F)
// index++
// // TODO chef: if the situation of multi sps exist?
// // only take the last one.
// for i := 0; i < numOfSPS; i++ {
// lenOfSPS := int(bele.BEUint16(buf[index:]))
// index += 2
// sps = append(sps, buf[index:index+lenOfSPS]...)
// index += lenOfSPS
// }
//
// numOfPPS := int(buf[index] & 0x1F)
// index++
// for i := 0; i < numOfPPS; i++ {
// lenOfPPS := int(bele.BEUint16(buf[index:]))
// index += 2
// pps = append(pps, buf[index:index+lenOfPPS]...)
// index += lenOfPPS
// }
//
// return
//}

@ -1,52 +0,0 @@
package httpflv
import (
"github.com/q191201771/lal/pkg/util/bele"
"github.com/q191201771/lal/pkg/util/log"
)
// TODO chef: move me to other packet
// H.264-AVC-ISO_IEC_14496-15.pdf
// 5.2.4 Decoder configuration information
// <buf> body of tag
func parseAVCSeqHeader(buf []byte) (sps, pps []byte, err error) {
// TODO chef: check if read out of <buf> range
if buf[0] != AVCKey || buf[1] != isAVCKeySeqHeader || buf[2] != 0 || buf[3] != 0 || buf[4] != 0 {
log.Error("parse avc seq header failed.")
err = httpFlvErr
return
}
//configurationVersion := buf[5]
//avcProfileIndication := buf[6]
//profileCompatibility := buf[7]
//avcLevelIndication := buf[8]
//lengthSizeMinusOne := buf[9] & 0x03
index := 10
numOfSPS := int(buf[index] & 0x1F)
index++
// TODO chef: if the situation of multi sps exist?
// only take the last one.
for i := 0; i < numOfSPS; i++ {
lenOfSPS := int(bele.BEUint16(buf[index:]))
index += 2
sps = append(sps, buf[index:index+lenOfSPS]...)
index += lenOfSPS
}
numOfPPS := int(buf[index] & 0x1F)
index++
for i := 0; i < numOfPPS; i++ {
lenOfPPS := int(bele.BEUint16(buf[index:]))
index += 2
pps = append(pps, buf[index:index+lenOfPPS]...)
index += lenOfPPS
}
return
}

@ -9,36 +9,36 @@ import (
const tagHeaderSize int = 11
const prevTagSizeFieldSize int = 4
var (
const (
TagTypeMetadata uint8 = 18
TagTypeVideo uint8 = 9
TagTypeAudio uint8 = 8
)
var (
const (
frameTypeKey uint8 = 1
frameTypeInter uint8 = 2
)
var (
const (
codecIDAVC uint8 = 7
)
var (
const (
AVCKey = frameTypeKey<<4 | codecIDAVC
AVCInter = frameTypeInter<<4 | codecIDAVC
)
var (
const (
isAVCKeySeqHeader uint8 = 0
AVCPacketTypeNalu uint8 = 1
)
var (
const (
SoundFormatAAC uint8 = 10
)
var (
const (
AACPacketTypeSeqHeader uint8 = 0
AACPacketTypeRaw uint8 = 1
)

@ -5,10 +5,10 @@ import (
)
// TODO chef: 这里所有的chunk的格式判断是参考的前一个message的字段。实际上应该参考当前message的前一个chunk的字段吧
func Message2Chunks(message []byte, header *Header, prevHeader *Header, chunkSize int) ([]byte, error) {
if header.CSID < minCSID || header.CSID > maxCSID {
return nil, rtmpErr
}
func Message2Chunks(message []byte, header *Header, prevHeader *Header, chunkSize int) []byte {
//if header.CSID < minCSID || header.CSID > maxCSID {
// return nil, rtmpErr
//}
// 计算chunk数量最后一个chunk的大小
numOfChunk := len(message) / chunkSize
@ -107,5 +107,5 @@ func Message2Chunks(message []byte, header *Header, prevHeader *Header, chunkSiz
}
}
return out[:index], nil
return out[:index]
}

@ -20,7 +20,13 @@ type Group struct {
subSessionSet map[*SubSession]struct{}
prevAudioHeader *Header
prevVideoHeader *Header
mutex sync.Mutex
// TODO chef:
metadata []byte
avcKeySeqHeader []byte
aacSeqHeader []byte
mutex sync.Mutex
obs GroupObserver
}
@ -67,12 +73,19 @@ func (group *Group) AddSubSession(session *SubSession) {
//group.turnToEmptyTick = 0
}
func (group *Group) DelRTMPPubSession(session *PubSession) {
// TODO chef: impl me
}
func (group *Group) DelPubSession(session *PubSession) {
log.Debugf("del PubSession from group. [%s]", session.UniqueKey)
group.mutex.Lock()
group.pubSession = nil
group.mutex.Unlock()
func (group *Group) DelRTMPSubSession(session *SubSession) {
}
func (group *Group) DelSubSession(session *SubSession) {
log.Debugf("del SubSession from group. [%s]", session.UniqueKey)
group.mutex.Lock()
delete(group.subSessionSet, session)
group.mutex.Unlock()
}
func (group *Group) Pull(addr string, connectTimeout int64) {
@ -124,47 +137,97 @@ func (group *Group) ReadRTMPAVMsgCB(header Header, timestampAbs int, message []b
}
func (group *Group) broadcastRTMP2RTMP(header Header, timestampAbs int, message []byte) {
//var (
// deltaChunks []byte
// absChunks []byte
//)
//log.Infof("%+v", header)
var currHeader Header
currHeader.MsgLen = len(message)
currHeader.Timestamp = timestampAbs
currHeader.MsgTypeID = header.MsgTypeID
currHeader.MsgStreamID = MSID1
var prevHeader *Header
//var prevHeader *Header
switch header.MsgTypeID {
case TypeidDataMessageAMF0:
currHeader.CSID = CSIDAMF
prevHeader = nil
//prevHeader = nil
case TypeidAudio:
currHeader.CSID = CSIDAudio
prevHeader = group.prevAudioHeader
//prevHeader = group.prevAudioHeader
case TypeidVideo:
currHeader.CSID = CSIDVideo
prevHeader = group.prevVideoHeader
//prevHeader = group.prevVideoHeader
}
// to be continued
// TODO chef: 如果是新加入的Sub
// TODO chef: 所有都使用abs格式了
var absChunks []byte
chunks, err := Message2Chunks(message, &currHeader, prevHeader, LocalChunkSize)
if err != nil {
log.Error(err)
return
}
for session := range group.subSessionSet {
session.WriteRawMessage(chunks)
if absChunks == nil {
absChunks = Message2Chunks(message, &currHeader, nil, LocalChunkSize)
}
// 是新连接
if session.isFresh {
// 发送缓存的头部信息
if group.metadata != nil {
session.AsyncWrite(group.metadata)
}
if group.avcKeySeqHeader != nil {
session.AsyncWrite(group.avcKeySeqHeader)
}
if group.aacSeqHeader != nil {
session.AsyncWrite(group.aacSeqHeader)
}
session.isFresh = false
} else {
// 首次发送从I帧开始
if session.waitKeyNalu {
if header.MsgTypeID == TypeidDataMessageAMF0 {
session.AsyncWrite(absChunks)
} else if header.MsgTypeID == TypeidAudio {
if (message[0]>>4) == 0x0a && message[1] == 0x0 {
session.AsyncWrite(absChunks)
}
} else if header.MsgTypeID == TypeidVideo {
if message[0] == 0x17 && message[1] == 0x0 {
session.AsyncWrite(absChunks)
}
if message[0] == 0x17 && message[1] == 0x1 {
session.AsyncWrite(absChunks)
session.waitKeyNalu = false
}
}
} else {
session.AsyncWrite(absChunks)
}
}
}
switch header.MsgTypeID {
case TypeidAudio:
prevHeader = &currHeader
case TypeidDataMessageAMF0:
if absChunks == nil {
absChunks = Message2Chunks(message, &currHeader, nil, LocalChunkSize)
}
log.Debug("cache metadata.")
group.metadata = absChunks
case TypeidVideo:
prevHeader = &currHeader
// TODO chef: magic number
if message[0] == 0x17 && message[1] == 0x0 {
if absChunks == nil {
absChunks = Message2Chunks(message, &currHeader, nil, LocalChunkSize)
}
log.Debug("cache avc key seq header.")
group.avcKeySeqHeader = absChunks
}
case TypeidAudio:
if (message[0]>>4) == 0x0a && message[1] == 0x0 {
if absChunks == nil {
absChunks = Message2Chunks(message, &currHeader, nil, LocalChunkSize)
}
log.Debug("cache aac seq header.")
group.aacSeqHeader = absChunks
}
}
}

@ -53,8 +53,7 @@ func (server *Server) Dispose() {
func (server *Server) handleConnect(conn net.Conn) {
log.Infof("accept a rtmp connection. remoteAddr=%v", conn.RemoteAddr())
session := NewServerSession(server, conn)
// TODO chef: 处理连接关闭
session.RunLoop()
_ = session.RunLoop()
}
func (server *Server) getOrCreateGroup(appName string, streamName string) *Group {
@ -75,7 +74,8 @@ func (server *Server) NewRTMPPubSessionCB(session *PubSession) {
group := server.getOrCreateGroup(session.AppName, session.StreamName)
if !server.obs.NewRTMPPubSessionCB(session, group) {
// TODO chef: 关闭这个连接
log.Warnf("dispose PubSession since pub exist.")
session.Dispose()
return
}
group.AddPubSession(session)
@ -91,3 +91,22 @@ func (server *Server) NewRTMPSubSessionCB(session *SubSession) {
}
group.AddSubSession(session)
}
// ServerSessionObserver
func (server *Server) DelRTMPPubSessionCB(session *PubSession) {
group := server.getOrCreateGroup(session.AppName, session.StreamName)
// TODO chef: obs
group.DelPubSession(session)
}
// ServerSessionObserver
func (server *Server) DelRTMPSubSessionCB(session *SubSession) {
group := server.getOrCreateGroup(session.AppName, session.StreamName)
// TODO chef: obs
group.DelSubSession(session)
}

@ -7,15 +7,21 @@ import (
"github.com/q191201771/lal/pkg/util/unique"
"net"
"strings"
"sync"
"sync/atomic"
)
// TODO chef: PubSession SubSession
// TODO chef: 没有进化成Pub Sub时的超时释放
var wChanSize = 1024 // TODO chef
type ServerSessionObserver interface {
NewRTMPPubSessionCB(session *PubSession) // 上层代码应该在这个事件回调中注册音视频数据的监听
NewRTMPSubSessionCB(session *SubSession)
DelRTMPPubSessionCB(session *PubSession)
DelRTMPSubSessionCB(session *SubSession)
}
type ServerSessionType int
@ -32,9 +38,6 @@ type ServerSession struct {
UniqueKey string
obs ServerSessionObserver
conn net.Conn
rb *bufio.Reader
wb *bufio.Writer
t ServerSessionType
hs HandshakeServer
chunkComposer *ChunkComposer
@ -42,32 +45,88 @@ type ServerSession struct {
// for PubSession
avObs PubSessionObserver
// to be continued
// TODO chef: 添加Dispose以及chan发送
conn net.Conn
rb *bufio.Reader
wb *bufio.Writer
wChan chan []byte
closeOnce sync.Once
exitChan chan struct{}
hasClosedFlag uint32
}
func NewServerSession(obs ServerSessionObserver, conn net.Conn) *ServerSession {
return &ServerSession{
UniqueKey: unique.GenUniqueKey("RTMPSERVER"),
obs: obs,
conn: conn,
rb: bufio.NewReaderSize(conn, readBufSize),
wb: bufio.NewWriterSize(conn, writeBufSize),
t: ServerSessionTypeInit,
chunkComposer: NewChunkComposer(),
packer: NewMessagePacker(),
UniqueKey: unique.GenUniqueKey("RTMPSERVER"),
conn: conn,
rb: bufio.NewReaderSize(conn, readBufSize),
wb: bufio.NewWriterSize(conn, writeBufSize),
wChan: make(chan []byte, wChanSize),
exitChan: make(chan struct{}),
}
}
func (s *ServerSession) RunLoop() error {
if err := s.handshake(); err != nil {
func (s *ServerSession) RunLoop() (err error) {
if err = s.handshake(); err != nil {
return err
}
go s.runWriteLoop()
if err = s.chunkComposer.RunLoop(s.rb, s.doMsg); err != nil {
s.dispose(err)
}
return err
}
func (s *ServerSession) Dispose() {
if atomic.LoadUint32(&s.hasClosedFlag) == 1 {
return
}
s.dispose(nil)
}
func (s *ServerSession) AsyncWrite(msg []byte) error {
if atomic.LoadUint32(&s.hasClosedFlag) == 1 {
return rtmpErr
}
s.wChan <- msg
return nil
}
func (s *ServerSession) runReadLoop() error {
return s.chunkComposer.RunLoop(s.rb, s.doMsg)
}
// TODO chef: 临时发送函数
func (s *ServerSession) WriteRawMessage(msg []byte) error {
_, err := s.conn.Write(msg)
return err
func (s *ServerSession) runWriteLoop() {
for {
select {
case <-s.exitChan:
return
case msg := <-s.wChan:
if _, err := s.conn.Write(msg); err != nil {
s.dispose(err)
}
return
}
}
}
func (s *ServerSession) dispose(err error) {
s.closeOnce.Do(func() {
atomic.StoreUint32(&s.hasClosedFlag, 1)
close(s.exitChan)
if err := s.conn.Close(); err != nil {
log.Errorf("conn close error. err=%v", err)
}
})
}
func (s *ServerSession) handshake() error {
@ -101,6 +160,8 @@ func (s *ServerSession) doMsg(stream *Stream) error {
}
//log.Infof("t:%d ts:%d len:%d", stream.header.MsgTypeID, stream.timestampAbs, stream.msg.e - stream.msg.b)
s.avObs.ReadRTMPAVMsgCB(stream.header, stream.timestampAbs, stream.msg.buf[stream.msg.b:stream.msg.e])
default:
log.Warnf("unknown message. typeid=%d", stream.header.MsgTypeID)
}
return nil

@ -2,10 +2,15 @@ package rtmp
type SubSession struct {
*ServerSession
isFresh bool
waitKeyNalu bool
}
func NewSubSession(ss *ServerSession) *SubSession {
return &SubSession{
ss,
ServerSession: ss,
isFresh: true,
waitKeyNalu: true,
}
}

@ -1,24 +1,29 @@
package bininfo
import "fmt"
import (
"fmt"
"runtime"
)
// 编译时通过如下方式传入编译时信息
// go build -ldflags " \
// -X 'github.com/q191201771/lal/pkg/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
// -X 'github.com/q191201771/lal/pkg/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
// -X 'github.com/q191201771/lal/pkg/bininfo.GoVersion=`go version`' \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildGoVersion=`go version`' \
// "
var (
BuildTime string
GitCommitID string
GoVersion string
BuildTime string
BuildGoVersion string
)
func StringifySingleLine() string {
return fmt.Sprintf("BuildTime: %s. GitCommitID: %s. GoVersion: %s.", BuildTime, GitCommitID, GoVersion)
return fmt.Sprintf("GitCommitID: %s. BuildTime: %s. GoVersion: %s. runtime: %s/%s",
GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}
func StringifyMultiLine() string {
return fmt.Sprintf("BuildTime: %s\nGitCommitID: %s\nGoVersion: %s\n", BuildTime, GitCommitID, GoVersion)
return fmt.Sprintf("GitCommitID: %s\nBuildTime: %s\nGoVersion: %s\nruntime: %s/%s",
GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}

@ -89,7 +89,7 @@ func init() {
if err != nil {
os.Exit(1)
}
err = log.SetAdditionalStackDepth(1)
err = log.SetAdditionalStackDepth(2)
if err != nil {
os.Exit(1)
}

Loading…
Cancel
Save