mirror of https://github.com/q191201771/lal.git
* PullSession和SubSession的主动关闭、被动关闭
* 引入日志库seelog,添加一些日志 * 缓存gop * 部分解析avc seq headerpull/200/head
parent
5a2ea4fadc
commit
bf6b9502df
@ -1,7 +1,10 @@
|
|||||||
/.idea
|
/demo/
|
||||||
/rtmp
|
/conf/self.conf.json
|
||||||
/demo
|
|
||||||
/conf/lal.conf.json
|
/rtmp/
|
||||||
/demo/httpflvpull/httpflvpull.go
|
/.idea/
|
||||||
|
/logs/
|
||||||
/TODO.md
|
/TODO.md
|
||||||
/lal
|
/lal
|
||||||
|
/lal_linux
|
||||||
|
/build_linux.sh
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"httpflv": {
|
||||||
|
"sub_listen_addr": ":8080",
|
||||||
|
"pull_addr": "pull.xxx.com",
|
||||||
|
"pull_connect_timeout": 2,
|
||||||
|
"pull_read_timeout": 20,
|
||||||
|
"sub_idle_timeout": 10,
|
||||||
|
"stop_pull_while_no_sub_timeout": 5,
|
||||||
|
"gop_cache_num": 2
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"httpflv": {
|
|
||||||
"sub_listen_addr": ":8080",
|
|
||||||
"pull_addr": "pull.x.com",
|
|
||||||
"pull_connect_timeout": 2,
|
|
||||||
"pull_read_timeout": 10,
|
|
||||||
"sub_idle_timeout": 10,
|
|
||||||
"stop_pull_while_no_sub_timeout": 30
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
<seelog minlevel="trace">
|
||||||
|
<outputs formatid="file_common">
|
||||||
|
<filter levels="trace">
|
||||||
|
<console formatid="console_trace" />
|
||||||
|
</filter>
|
||||||
|
<filter levels="debug">
|
||||||
|
<console formatid="console_debug" />
|
||||||
|
</filter>
|
||||||
|
<filter levels="info">
|
||||||
|
<console formatid="console_info" />
|
||||||
|
</filter>
|
||||||
|
<filter levels="warn">
|
||||||
|
<console formatid="console_warn" />
|
||||||
|
</filter>
|
||||||
|
<filter levels="error">
|
||||||
|
<console formatid="console_error" />
|
||||||
|
</filter>
|
||||||
|
<filter levels="critical">
|
||||||
|
<console formatid="console_critical" />
|
||||||
|
</filter>
|
||||||
|
<file path="./logs/lal.log" />
|
||||||
|
<filter levels="warn,error,critical">
|
||||||
|
<file path="./logs/lal_error.log" />
|
||||||
|
</filter>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="file_common" format="%Date(2006-01-02 15:04:05.000) %LEV %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_trace" format="%Date(2006-01-02 15:04:05.000) %EscM(37)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_debug" format="%Date(2006-01-02 15:04:05.000) %EscM(37)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_info" format="%Date(2006-01-02 15:04:05.000) %EscM(36)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_warn" format="%Date(2006-01-02 15:04:05.000) %EscM(33)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_error" format="%Date(2006-01-02 15:04:05.000) %EscM(31)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
<format id="console_critical" format="%Date(2006-01-02 15:04:05.000) %EscM(31)%LEV%EscM(49)%EscM(0) %Msg - %File:%Line%n" />
|
||||||
|
</formats>
|
||||||
|
</seelog>
|
@ -0,0 +1,11 @@
|
|||||||
|
<seelog minlevel="info">
|
||||||
|
<outputs formatid="common">
|
||||||
|
<rollingfile type="date" filename="./logs/lal.log" datepattern="2006-01-02" />
|
||||||
|
<filter levels="warn,error,critical">
|
||||||
|
<rollingfile type="date" filename="./logs/lal_error.log" datepattern="2006-01-02" />
|
||||||
|
</filter>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="common" format="%Date(2006-01-02 15:04:05) %LEV %Msg%n" />
|
||||||
|
</formats>
|
||||||
|
</seelog>
|
@ -0,0 +1,52 @@
|
|||||||
|
package httpflv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/q191201771/lal/bele"
|
||||||
|
"github.com/q191201771/lal/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] != AvcPacketTypeSeqHeader || buf[2] != 0 || buf[3] != 0 || buf[4] != 0 {
|
||||||
|
log.Error("parse avc seq header failed.")
|
||||||
|
err = fxxkErr
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,206 @@
|
|||||||
|
package httpflv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/q191201771/lal/log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gop struct {
|
||||||
|
raw []byte
|
||||||
|
firstTimestamp uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type GopCache struct {
|
||||||
|
gopNum int
|
||||||
|
|
||||||
|
metadata *Tag
|
||||||
|
avcSeqHeader *Tag
|
||||||
|
aacSeqHeader *Tag
|
||||||
|
gops []*Gop // TODO chef: maybe use other container to mock a queue
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// gopNum: 0 means only cache metadata, avc seq header, aac seq header
|
||||||
|
func NewGopCache(gopNum int) *GopCache {
|
||||||
|
return &GopCache{
|
||||||
|
gopNum: gopNum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) Push(tag *Tag) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if tag.isMetaData() {
|
||||||
|
// TODO chef: will this happen?
|
||||||
|
if c.metadata != nil {
|
||||||
|
log.Debugf("updating metadata.")
|
||||||
|
log.Debug(tag.Header, tag.Raw[tagHeaderSize:])
|
||||||
|
log.Debug(c.metadata.Header, c.metadata.Raw[tagHeaderSize:])
|
||||||
|
c.clearGop()
|
||||||
|
}
|
||||||
|
c.metadata = tag
|
||||||
|
}
|
||||||
|
if tag.isAvcKeySeqHeader() {
|
||||||
|
//log.Debug(parseAvcSeqHeader(tag.Raw[tagHeaderSize:]))
|
||||||
|
if c.avcSeqHeader == nil {
|
||||||
|
c.avcSeqHeader = tag
|
||||||
|
} else {
|
||||||
|
// TODO chef: compare nessary? if other way to update seq header and handle cache stuff?
|
||||||
|
if bytes.Compare(tag.Raw[tagHeaderSize:], c.avcSeqHeader.Raw[tagHeaderSize:]) == 0 {
|
||||||
|
// noop
|
||||||
|
} else {
|
||||||
|
log.Debugf("updating avc seq header.")
|
||||||
|
log.Debug(tag.Header, tag.Raw[tagHeaderSize:])
|
||||||
|
log.Debug(c.avcSeqHeader.Header, c.avcSeqHeader.Raw[tagHeaderSize:])
|
||||||
|
c.clearGop()
|
||||||
|
c.avcSeqHeader = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tag.isAacSeqHeader() {
|
||||||
|
if c.aacSeqHeader == nil {
|
||||||
|
c.aacSeqHeader = tag
|
||||||
|
} else {
|
||||||
|
if bytes.Compare(tag.Raw[tagHeaderSize:], c.aacSeqHeader.Raw[tagHeaderSize:]) == 0 {
|
||||||
|
// noop
|
||||||
|
} else {
|
||||||
|
log.Debugf("updating aac seq header.")
|
||||||
|
c.clearGop()
|
||||||
|
c.aacSeqHeader = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.aacSeqHeader = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.gopNum == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.gops) == 0 {
|
||||||
|
if tag.isAvcKeyNalu() {
|
||||||
|
gop := &Gop{}
|
||||||
|
gop.firstTimestamp = tag.Header.Timestamp
|
||||||
|
gop.raw = append(gop.raw, tag.Raw...)
|
||||||
|
c.gops = append(c.gops, gop)
|
||||||
|
c.syncOldestKeyNaluTimestampToSeqHeader()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tag.isAvcKeyNalu() {
|
||||||
|
gop := &Gop{}
|
||||||
|
gop.firstTimestamp = tag.Header.Timestamp
|
||||||
|
gop.raw = append(gop.raw, tag.Raw...)
|
||||||
|
c.gops = append(c.gops, gop)
|
||||||
|
if len(c.gops) > c.gopNum+1 {
|
||||||
|
c.gops = c.gops[1:]
|
||||||
|
c.syncOldestKeyNaluTimestampToSeqHeader()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.gops[len(c.gops)-1].raw = append(c.gops[len(c.gops)-1].raw, tag.Raw...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) GetWholeThings() (hasKeyFrame bool, res []byte) {
|
||||||
|
if tag := c.getMetadata(); tag != nil {
|
||||||
|
res = append(res, tag.Raw...)
|
||||||
|
}
|
||||||
|
|
||||||
|
avc := c.getAvcSeqHeader()
|
||||||
|
aac := c.getAacSeqHeader()
|
||||||
|
// TODO chef: if nessary to sort them by timestamp
|
||||||
|
if avc != nil && aac != nil {
|
||||||
|
if avc.Header.Timestamp <= aac.Header.Timestamp {
|
||||||
|
res = append(res, avc.Raw...)
|
||||||
|
res = append(res, aac.Raw...)
|
||||||
|
} else {
|
||||||
|
res = append(res, aac.Raw...)
|
||||||
|
res = append(res, avc.Raw...)
|
||||||
|
}
|
||||||
|
} else if avc != nil && aac == nil {
|
||||||
|
res = append(res, avc.Raw...)
|
||||||
|
} else if avc == nil && aac != nil {
|
||||||
|
res = append(res, aac.Raw...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gops := c.getGops(false); gops != nil {
|
||||||
|
res = append(res, gops...)
|
||||||
|
log.Debug("cache match.")
|
||||||
|
hasKeyFrame = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) ClearAll() {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
c.metadata = nil
|
||||||
|
c.avcSeqHeader = nil
|
||||||
|
c.aacSeqHeader = nil
|
||||||
|
c.gops = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) getGops(mustCompleted bool) []byte {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
neededLen := len(c.gops)
|
||||||
|
if mustCompleted {
|
||||||
|
neededLen--
|
||||||
|
}
|
||||||
|
if neededLen <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte
|
||||||
|
for i := 0; i != neededLen; i++ {
|
||||||
|
res = append(res, c.gops[i].raw...)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) getMetadata() (res *Tag) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
if c.metadata != nil {
|
||||||
|
res = c.metadata.cloneTag()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) getAvcSeqHeader() (res *Tag) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.avcSeqHeader != nil {
|
||||||
|
res = c.avcSeqHeader.cloneTag()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) getAacSeqHeader() (res *Tag) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.aacSeqHeader != nil {
|
||||||
|
res = c.aacSeqHeader.cloneTag()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GopCache) clearGop() {
|
||||||
|
log.Debug("clearGop")
|
||||||
|
c.gops = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO chef: if nessary
|
||||||
|
func (c *GopCache) syncOldestKeyNaluTimestampToSeqHeader() {
|
||||||
|
ts := c.gops[0].firstTimestamp
|
||||||
|
if c.avcSeqHeader != nil {
|
||||||
|
c.avcSeqHeader.Header.Timestamp = ts
|
||||||
|
}
|
||||||
|
if c.aacSeqHeader != nil {
|
||||||
|
c.aacSeqHeader.Header.Timestamp = ts
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
var log seelog.LoggerInterface
|
||||||
|
|
||||||
|
func Initial(configFileName string) error {
|
||||||
|
var err error
|
||||||
|
log, err = seelog.LoggerFromConfigAsFile(configFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = log.SetAdditionalStackDepth(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, params ...interface{}) {
|
||||||
|
log.Debugf(format, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, params ...interface{}) {
|
||||||
|
log.Infof(format, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnf(format string, params ...interface{}) {
|
||||||
|
log.Warnf(format, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, params ...interface{}) {
|
||||||
|
log.Errorf(format, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
log.Debug(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
log.Info(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
log.Warn(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
log.Error(v...)
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalId uint64
|
||||||
|
|
||||||
|
func GenUniqueId() uint64 {
|
||||||
|
return atomic.AddUint64(&globalId, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenUniqueKey(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s%d", prefix, GenUniqueId())
|
||||||
|
}
|
Loading…
Reference in New Issue