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

257 lines
7.3 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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"
"os"
"strconv"
"strings"
"time"
"github.com/q191201771/naza/pkg/nazastring"
"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() {
url := parseFlag()
session := httpflv.NewPullSession()
brTotal := bitrate.New()
brAudio := bitrate.New()
brVideo := bitrate.New()
videoCTSNotZeroCount := 0
go func() {
for {
time.Sleep(1 * 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 {
debugLength := 32
if len(tag.Raw) < 32 {
debugLength = len(tag.Raw)
}
nazalog.Debugf("header=%+v, hex=%s", tag.Header, hex.Dump(tag.Raw[11:debugLength]))
}
brTotal.Add(len(tag.Raw))
switch tag.Header.Type {
case httpflv.TagTypeMetadata:
if printMetaData {
nazalog.Debugf("----------\n%s", hex.Dump(tag.Raw[11:]))
opa, err := rtmp.ParseMetadata(tag.Raw[11 : len(tag.Raw)-4])
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:
brAudio.Add(len(tag.Raw))
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.Warn(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] ")
if _, _, err := avc.ParseSPSPPSFromSeqHeader(tag.Raw[11:]); err != nil {
buf.WriteString(" parse sps pps failed.")
}
} else if tag.IsHEVCKeySeqHeader() {
t = typeHEVC
//nazalog.Debugf("%s", nazastring.DumpSliceByte(tag.Raw[11:]))
vps, sps, pps, _ := hevc.ParseVPSSPSPPSFromSeqHeader(tag.Raw[11:])
nazalog.Debugf("%s", nazastring.DumpSliceByte(vps))
nazalog.Debugf("%s", nazastring.DumpSliceByte(sps))
nazalog.Debugf("%s", nazastring.DumpSliceByte(pps))
//nazalog.Debugf("%s %s %s %+v", hex.Dump(vps), hex.Dump(sps), hex.Dump(pps), err)
buf.WriteString(" [HEVC SeqHeader] ")
}
} else {
body := tag.Raw[11:]
i := 5
for i != int(tag.Header.DataSize) {
if i+4 > int(tag.Header.DataSize) {
nazalog.Errorf("invalid nalu size. i=%d, tag size=%d", i, int(tag.Header.DataSize))
break
}
naluLen := bele.BEUint32(body[i:])
if i+int(naluLen) > int(tag.Header.DataSize) {
nazalog.Errorf("invalid nalu size. i=%d, naluLen=%d, tag size=%d", i, naluLen, int(tag.Header.DataSize))
break
}
switch t {
case typeAVC:
if avc.ParseNALUType(body[i+4]) == avc.NALUTypeIDRSlice {
if prevIDRTS != int64(-1) {
diffIDRTS = int64(tag.Header.Timestamp) - prevIDRTS
}
prevIDRTS = int64(tag.Header.Timestamp)
}
if avc.ParseNALUType(body[i+4]) == avc.NALUTypeSEI {
delay := SEIDelayMS(body[i+4 : i+4+int(naluLen)])
if delay != -1 {
buf.WriteString(fmt.Sprintf("delay: %dms", delay))
}
}
sliceTypeReadable, _ := avc.ParseSliceTypeReadable(body[i+4:])
buf.WriteString(fmt.Sprintf(" [%s(%s)] ", avc.ParseNALUTypeReadable(body[i+4]), sliceTypeReadable))
case typeHEVC:
if hevc.ParseNALUType(body[i+4]) == hevc.NALUTypeSEI {
delay := SEIDelayMS(body[i+4 : i+4+int(naluLen)])
if delay != -1 {
buf.WriteString(fmt.Sprintf("delay: %dms", delay))
}
}
buf.WriteString(fmt.Sprintf(" [%s] ", hevc.ParseNALUTypeReadable(body[i+4])))
}
i = i + 4 + int(naluLen)
}
}
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()
os.Exit(1)
}
return *url
}