You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/app/demo/analyseflv/analyseflv.go

275 lines
7.7 KiB
Go

// Copyright 2019, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package main
import (
"bytes"
"encoding/hex"
"flag"
"fmt"
"strconv"
"strings"
"time"
3 years ago
"github.com/q191201771/naza/pkg/nazabytes"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/rtmp"
"github.com/q191201771/lal/pkg/avc"
"github.com/q191201771/lal/pkg/hevc"
"github.com/q191201771/naza/pkg/bele"
"github.com/q191201771/naza/pkg/bitrate"
"github.com/q191201771/lal/pkg/httpflv"
"github.com/q191201771/naza/pkg/nazalog"
)
// 分析诊断HTTP-FLV流的时间戳。注意这个程序还没有完成。
// 功能:
// - 时间戳回退检查
// - 当音频时间戳出现回退时打error日志
// - 当视频时间戳出现回退时打error日志
// - 将音频和视频时间戳看成一个整体出现回退时打error日志
// - 定时打印:
// - 总体带宽
// - 音频带宽
// - 视频带宽
// - 视频DTS和PTS不相等的计数
// - I帧间隔时间
// - H264
// - 打印每个tag的类型key seq header...
// - 打印每个tag中有多少个帧SPS PPS SEI IDR SLICE...
// - 打印每个SLICE的类型I、P、B...
// 解析metadata信息并打印
// TODO
// - 检查时间戳正向大的跳跃
// - 打印GOP中帧数量
// - slice_num?
// - 输入源可以是httpflv也可以是flv文件
var (
timestampCheckFlag = true
printStatFlag = true
printEveryTagFlag = false
printMetaData = true
analysisVideoTagFlag = true
)
var (
prevAudioTs = int64(-1)
prevVideoTs = int64(-1)
prevTs = int64(-1)
prevIdrTs = int64(-1)
diffIdrTs = int64(-1)
)
func main() {
_ = nazalog.Init(func(option *nazalog.Option) {
option.AssertBehavior = nazalog.AssertFatal
})
defer nazalog.Sync()
base.LogoutStartInfo()
url := parseFlag()
session := httpflv.NewPullSession()
brTotal := bitrate.New(func(option *bitrate.Option) {
option.WindowMs = 5000
})
brAudio := bitrate.New(func(option *bitrate.Option) {
option.WindowMs = 5000
})
brVideo := bitrate.New(func(option *bitrate.Option) {
option.WindowMs = 5000
})
videoCtsNotZeroCount := 0
go func() {
for {
time.Sleep(5 * time.Second)
if printStatFlag {
nazalog.Debugf("stat. total=%dKb/s, audio=%dKb/s, video=%dKb/s, videoCtsNotZeroCount=%d, diffIdrTs=%d",
int(brTotal.Rate()), int(brAudio.Rate()), int(brVideo.Rate()), videoCtsNotZeroCount, diffIdrTs)
}
}
}()
err := session.Pull(url, func(tag httpflv.Tag) {
if printEveryTagFlag {
3 years ago
nazalog.Debugf("header=%+v, hex=%s", tag.Header, hex.Dump(nazabytes.Prefix(tag.Payload(), 32)))
}
brTotal.Add(len(tag.Raw))
switch tag.Header.Type {
case httpflv.TagTypeMetadata:
if printMetaData {
nazalog.Debugf("----------\n%s", hex.Dump(tag.Payload()))
opa, err := rtmp.ParseMetadata(tag.Payload())
nazalog.Assert(nil, err)
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("-----\ncount:%d\n", len(opa)))
for _, op := range opa {
buf.WriteString(fmt.Sprintf(" %s: %+v\n", op.Key, op.Value))
}
nazalog.Debugf("%+v", buf.String())
}
case httpflv.TagTypeAudio:
nazalog.Debugf("header=%+v, %+v", tag.Header, tag.IsAacSeqHeader())
brAudio.Add(len(tag.Raw))
if tag.IsAacSeqHeader() {
s := session.GetStat()
nazalog.Infof("aac seq header. readBytes=%d, %s", s.ReadBytesSum, hex.EncodeToString(tag.Payload()))
}
if timestampCheckFlag {
if prevAudioTs != -1 && int64(tag.Header.Timestamp) < prevAudioTs {
nazalog.Errorf("audio timestamp error, less than prev audio timestamp. header=%+v, prevAudioTs=%d, diff=%d", tag.Header, prevAudioTs, int64(tag.Header.Timestamp)-prevAudioTs)
}
if prevTs != -1 && int64(tag.Header.Timestamp) < prevTs {
nazalog.Warnf("audio timestamp error. less than prev global timestamp. header=%+v, prevTs=%d, diff=%d", tag.Header, prevTs, int64(tag.Header.Timestamp)-prevTs)
}
}
prevAudioTs = int64(tag.Header.Timestamp)
prevTs = int64(tag.Header.Timestamp)
case httpflv.TagTypeVideo:
analysisVideoTag(tag)
videoCts := bele.BeUint24(tag.Raw[13:])
if videoCts != 0 {
videoCtsNotZeroCount++
}
brVideo.Add(len(tag.Raw))
if timestampCheckFlag {
if prevVideoTs != -1 && int64(tag.Header.Timestamp) < prevVideoTs {
nazalog.Errorf("video timestamp error, less than prev video timestamp. header=%+v, prevVideoTs=%d, diff=%d", tag.Header, prevVideoTs, int64(tag.Header.Timestamp)-prevVideoTs)
}
if prevTs != -1 && int64(tag.Header.Timestamp) < prevTs {
nazalog.Warnf("video timestamp error, less than prev global timestamp. header=%+v, prevTs=%d, diff=%d", tag.Header, prevTs, int64(tag.Header.Timestamp)-prevTs)
}
}
prevVideoTs = int64(tag.Header.Timestamp)
prevTs = int64(tag.Header.Timestamp)
}
})
nazalog.Assert(nil, err)
// 临时测试一下主动关闭client session
//go func() {
// time.Sleep(5 * time.Second)
// _ = session.Dispose()
//}()
4 years ago
err = <-session.WaitChan()
nazalog.Errorf("< session.WaitChan. err=%+v", err)
}
const (
typeUnknown uint8 = 1
typeAvc uint8 = 2
typeHevc uint8 = 3
)
var t uint8 = typeUnknown
func analysisVideoTag(tag httpflv.Tag) {
var buf bytes.Buffer
if tag.IsVideoKeySeqHeader() {
if tag.IsAvcKeySeqHeader() {
t = typeAvc
buf.WriteString(" [AVC SeqHeader] ")
sps, pps, err := avc.ParseSpsPpsFromSeqHeader(tag.Payload())
if err != nil {
buf.WriteString(" parse sps pps failed.")
}
nazalog.Debugf("sps:%s, pps:%s", hex.Dump(sps), hex.Dump(pps))
} else if tag.IsHevcKeySeqHeader() {
t = typeHevc
buf.WriteString(" [HEVC SeqHeader] ")
buf.WriteString(hex.Dump(tag.Payload()))
if _, _, _, err := hevc.ParseVpsSpsPpsFromSeqHeader(tag.Payload()); err != nil {
buf.WriteString(" parse vps sps pps failed.")
}
}
} else {
cts := bele.BeUint24(tag.Payload()[2:])
buf.WriteString(fmt.Sprintf("%+v, cts=%d, pts=%d", tag.Header, cts, tag.Header.Timestamp+cts))
body := tag.Payload()[5:]
nals, err := avc.SplitNaluAvcc(body)
nazalog.Assert(nil, err)
for _, nal := range nals {
switch t {
case typeAvc:
if avc.ParseNaluType(nal[0]) == avc.NaluTypeIdrSlice {
3 years ago
nazalog.Debugf("IDR:%s", hex.Dump(nazabytes.Prefix(nal, 128)))
if prevIdrTs != int64(-1) {
diffIdrTs = int64(tag.Header.Timestamp) - prevIdrTs
}
prevIdrTs = int64(tag.Header.Timestamp)
}
if avc.ParseNaluType(nal[0]) == avc.NaluTypeSei {
delay := SeiDelayMs(nal)
if delay != -1 {
buf.WriteString(fmt.Sprintf("delay: %dms", delay))
}
}
sliceTypeReadable, _ := avc.ParseSliceTypeReadable(nal)
buf.WriteString(fmt.Sprintf(" [%s(%s)(%d)] ", avc.ParseNaluTypeReadable(nal[0]), sliceTypeReadable, len(nal)))
case typeHevc:
if hevc.ParseNaluType(nal[0]) == hevc.NaluTypeSei {
delay := SeiDelayMs(nal)
if delay != -1 {
buf.WriteString(fmt.Sprintf("delay: %dms", delay))
}
}
buf.WriteString(fmt.Sprintf(" [%s(%d)] ", hevc.ParseNaluTypeReadable(nal[0]), nal[0]))
}
}
}
if analysisVideoTagFlag {
nazalog.Debug(buf.String())
}
}
// 注意SEI的内容是自定义格式解析的代码不具有通用性
func SeiDelayMs(seiNalu []byte) int {
//nazalog.Debugf("sei: %s", hex.Dump(seiNalu))
items := strings.Split(string(seiNalu), ":")
if len(items) != 3 {
return -1
}
a, err := strconv.ParseInt(items[1], 10, 64)
if err != nil {
return -1
}
t := time.Unix(a/1e3, a%1e3)
d := time.Now().Sub(t)
return int(d.Nanoseconds() / 1e6)
}
func parseFlag() string {
url := flag.String("i", "", "specify http-flv url")
flag.Parse()
if *url == "" {
flag.Usage()
base.OsExitAndWaitPressIfWindows(1)
}
return *url
}