From: qydysky Date: Mon, 13 Jan 2025 14:14:38 +0000 (+0800) Subject: Add flv快速索引文件 (#147) X-Git-Tag: v0.15.0~2 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=9eef8a9de5af7a457361a48913cf92aefc6a62b5;p=bili_danmu%2F.git Add flv快速索引文件 (#147) * Add flv快速索引文件 * Fix flv示例 * Improve workflow --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8fa5a27..55c21f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,8 @@ on: - '**.go' - '**.mod' - '**.sum' + - '**.flv' + - '**.mp4' jobs: buildtest: diff --git a/README.md b/README.md index dde3534..77720da 100644 --- a/README.md +++ b/README.md @@ -163,10 +163,13 @@ Warning: Binary output can mess up your terminal. Use "--output -" to tell curl * closing connection #0 ``` -添加快速索引文件生成,将在录制完成后,读取视频文件,并将关键帧的时间戳和对应的下标值记录在`.fastSeed`文件,用于加快后续切片请求响应。(>v0.14.28) +添加快速索引文件生成,将在录制完成后,自定义回调命令执行前,读取视频文件,并将关键帧的时间戳和对应的下标值记录在`.fastSeed`文件,用于加快后续切片请求响应。(>v0.14.28) -相较于之前的请求时进行查找,效率提升如下(以mp4格式为例) +可以设置`禁用快速索引生成`(默认为`false`)为`true`来禁用这个动作(>v0.14.28) + +相较于之前的请求时进行查找,效率提升如下 ``` +mp4: 旧: // 10s-30s 110.962896ms // 10m-10m20s 1.955749395s @@ -176,6 +179,15 @@ Warning: Binary output can mess up your terminal. Use "--output -" to tell curl // 10s-30s 90.05983ms // 10m-10m20s 88.769475ms // 30m-30m20s 104.381225ms + +flv: +旧: +// 10s-30s 184.852917ms +// 10m-10m20s 3.278605875s + +新: +// 10s-30s 215.815423ms +// 10m-10m20s 174.918508ms ``` `.fastSeed`文件格式: diff --git a/Reply/F.go b/Reply/F.go index d5547bd..786cdde 100644 --- a/Reply/F.go +++ b/Reply/F.go @@ -1201,8 +1201,17 @@ func init() { if v, ok := c.C.K_v.LoadV(`flv音视频时间戳容差ms`).(float64); ok && v > 100 { flvDecoder.Diff = v } - if e := flvDecoder.Cut(f, startT, duration, res); e != nil && !errors.Is(e, io.EOF) { - flog.L(`E: `, e) + // fastSeed + if fastSeedF := file.New(v+".fastSeed", 0, true); fastSeedF.IsExist() { + if gf, e := replyFunc.VideoFastSeed.InitGet(v + ".fastSeed"); e != nil { + flog.L(`E: `, e) + } else if e := flvDecoder.CutSeed(f, startT, duration, res, f, gf); e != nil && !errors.Is(e, io.EOF) { + flog.L(`E: `, e) + } + } else { + if e := flvDecoder.Cut(f, startT, duration, res); e != nil && !errors.Is(e, io.EOF) { + flog.L(`E: `, e) + } } } if strings.HasSuffix(v, "mp4") { diff --git a/Reply/emots/README.md b/Reply/emots/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Reply/flvDecode.go b/Reply/flvDecode.go index b78880c..068e32f 100644 --- a/Reply/flvDecode.go +++ b/Reply/flvDecode.go @@ -201,7 +201,9 @@ func (t *FlvDecoder) SearchStreamTag(buf []byte, keyframe *slice.Buf[byte]) (dro return } -func (t *FlvDecoder) oneF(buf []byte, ifWrite func(t int) bool, w ...io.Writer) (dropOffset int, err error) { +type dealFFlv func(t int, index int, buf []byte) error + +func (t *FlvDecoder) oneF(buf []byte, w ...dealFFlv) (dropOffset int, err error) { if !t.init { err = ErrNoInit @@ -263,9 +265,7 @@ func (t *FlvDecoder) oneF(buf []byte, ifWrite func(t int) bool, w ...io.Writer) if buf[bufOffset] == videoTag && buf[bufOffset+11]&0xf0 == 0x10 { //key frame if keyframeOp >= 0 && len(w) > 0 { dropOffset = bufOffset - if ifWrite(timeStamp) { - _, err = w[0].Write(buf[keyframeOp:bufOffset]) - } + err = w[0](timeStamp, keyframeOp, buf[keyframeOp:bufOffset]) return } keyframeOp = bufOffset @@ -276,25 +276,31 @@ func (t *FlvDecoder) oneF(buf []byte, ifWrite func(t int) bool, w ...io.Writer) return } +// Deprecated: 效率低于GenFastSeed+CutSeed func (t *FlvDecoder) Cut(reader io.Reader, startT, duration time.Duration, w io.Writer) (err error) { + return t.CutSeed(reader, startT, duration, w, nil, nil) +} + +func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w io.Writer, seeker io.Seeker, getIndex func(seedTo time.Duration) (int64, error)) (err error) { bufSize := humanize.KByte * 1100 buf := make([]byte, humanize.KByte*500) buff := slice.New[byte]() over := false + seek := false startTM := startT.Milliseconds() durationM := duration.Milliseconds() firstFT := -1 - ifWriteF := func(t int) bool { + wf := func(t int, index int, buf []byte) (e error) { if firstFT == -1 { firstFT = t } cu := int64(t - firstFT) over = duration != 0 && cu > durationM+startTM if startTM <= cu && !over { - return true + _, e = w.Write(buf) } - return false + return } for c := 0; err == nil && !over; c++ { @@ -323,7 +329,71 @@ func (t *FlvDecoder) Cut(reader io.Reader, startT, duration time.Duration, w io. } } } else { - if dropOffset, e := t.oneF(buff.GetPureBuf(), ifWriteF, w); e != nil { + if !seek && seeker != nil && getIndex != nil { + if index, e := getIndex(startT); e != nil { + return perrors.New("s", e.Error()) + } else { + if _, e := seeker.Seek(index, io.SeekStart); e != nil { + return perrors.New("s", e.Error()) + } + } + seek = true + startTM = 0 + buff.Clear() + } + if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil { + return perrors.New("skip", e.Error()) + } else { + if dropOffset != 0 { + _ = buff.RemoveFront(dropOffset) + } else { + bufSize *= 2 + } + } + } + } + return +} + +func (t *FlvDecoder) GenFastSeed(reader io.Reader, save func(seedTo time.Duration, cuIndex int64) error) (err error) { + bufSize := humanize.KByte * 1100 + totalRead := 0 + buf := make([]byte, humanize.KByte*500) + buff := slice.New[byte]() + over := false + firstFT := -1 + + for c := 0; err == nil && !over; c++ { + if buff.Size() < bufSize { + n, e := reader.Read(buf) + if n == 0 && errors.Is(e, io.EOF) { + return io.EOF + } + totalRead += n + err = buff.Append(buf[:n]) + continue + } + + if !t.init { + if frontBuf, dropOffset, e := t.InitFlv(buff.GetPureBuf()); e != nil { + return perrors.New("InitFlv", e.Error()) + } else { + if dropOffset != 0 { + _ = buff.RemoveFront(dropOffset) + } else { + bufSize *= 2 + } + if len(frontBuf) == 0 { + continue + } + } + } else { + if dropOffset, e := t.oneF(buff.GetPureBuf(), func(t, index int, buf []byte) error { + if firstFT == -1 { + firstFT = t + } + return save(time.Millisecond*time.Duration(t-firstFT), int64(totalRead-buff.Size()+index)) + }); e != nil { return perrors.New("skip", e.Error()) } else { if dropOffset != 0 { diff --git a/Reply/flvDecode_test.go b/Reply/flvDecode_test.go index 109cd71..fb49951 100644 --- a/Reply/flvDecode_test.go +++ b/Reply/flvDecode_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/dustin/go-humanize" + comp "github.com/qydysky/part/component2" perrors "github.com/qydysky/part/errors" file "github.com/qydysky/part/file" slice "github.com/qydysky/part/slice" @@ -51,20 +52,108 @@ func Test_FLVdeal(t *testing.T) { t.Log("max", humanize.Bytes(uint64(max))) } +// 10s-30s 184.852917ms +// 10m-10m20s 3.278605875s func Test_FLVCut(t *testing.T) { + { + st := time.Now() + defer func() { + fmt.Println(time.Since(st)) + }() + } + cutf := file.New("testdata/0.cut.flv", 0, false) + defer cutf.Close() + _ = cutf.Delete() + + f := file.New("testdata/0.flv", 0, false) + defer f.Close() + + if f.IsDir() || !f.IsExist() { + t.Log("test file not exist") + } + + e := NewFlvDecoder().Cut(f, time.Minute*10, time.Second*20, cutf.File()) + if perrors.Catch(e, "Read") { + t.Log("err Read", e) + } + if perrors.Catch(e, "InitFlv") { + t.Log("err InitFlv", e) + } + if perrors.Catch(e, "skip") { + t.Log("err skip", e) + } + if perrors.Catch(e, "cutW") { + t.Log("err cutW", e) + } + t.Log(e) +} + +func Test_FLVGenFastSeed(t *testing.T) { + var VideoFastSeed = comp.Get[interface { + InitGet(fastSeedFilePath string) (getIndex func(seedTo time.Duration) (int64, error), e error) + InitSav(fastSeedFilePath string) (savIndex func(seedTo time.Duration, cuIndex int64) error, e error) + }](`videoFastSeed`) + + f := file.New("testdata/0.flv", 0, false) + defer f.Close() + sf, e := VideoFastSeed.InitSav("testdata/0.flv.fastSeed") + if e != nil { + t.Fatal(e) + } + + if f.IsDir() || !f.IsExist() { + t.Log("test file not exist") + } - cutf := file.New("testdata/1.cut.flv", 0, false) + e = NewFlvDecoder().GenFastSeed(f, func(seedTo time.Duration, cuIndex int64) error { + return sf(seedTo, cuIndex) + }) + if perrors.Catch(e, "Read") { + t.Log("err Read", e) + } + if perrors.Catch(e, "InitFlv") { + t.Log("err InitFlv", e) + } + if perrors.Catch(e, "skip") { + t.Log("err skip", e) + } + if perrors.Catch(e, "cutW") { + t.Log("err cutW", e) + } + t.Log(e) +} + +// 10s-30s 215.815423ms +// 10m-10m20s 174.918508ms +func Test_FLVCutSeed(t *testing.T) { + { + st := time.Now() + defer func() { + fmt.Println(time.Since(st)) + }() + } + cutf := file.New("testdata/0.cut.flv", 0, false) defer cutf.Close() _ = cutf.Delete() - f := file.New("testdata/1.flv", 0, false) + f := file.New("testdata/0.flv", 0, false) defer f.Close() if f.IsDir() || !f.IsExist() { t.Log("test file not exist") } - e := NewFlvDecoder().Cut(f, time.Second*10, time.Second*20, cutf.File()) + var VideoFastSeed = comp.Get[interface { + InitGet(fastSeedFilePath string) (getIndex func(seedTo time.Duration) (int64, error), e error) + InitSav(fastSeedFilePath string) (savIndex func(seedTo time.Duration, cuIndex int64) error, e error) + }](`videoFastSeed`) + + gf, e := VideoFastSeed.InitGet("testdata/0.flv.fastSeed") + if e != nil { + t.Fatal(e) + } + + e = NewFlvDecoder().CutSeed(f, time.Minute*10, time.Second*20, cutf.File(), f, gf) if perrors.Catch(e, "Read") { t.Log("err Read", e) } diff --git a/Reply/fmp4Decode.go b/Reply/fmp4Decode.go index 8c5eccd..a2bbf89 100644 --- a/Reply/fmp4Decode.go +++ b/Reply/fmp4Decode.go @@ -405,9 +405,9 @@ func (t *Fmp4Decoder) Search_stream_fmp4(buf []byte, keyframe *slice.Buf[byte]) return } -type dealF func(t float64, index int, buf *slice.Buf[byte]) error +type dealFMp4 func(t float64, index int, buf *slice.Buf[byte]) error -func (t *Fmp4Decoder) oneF(buf []byte, w ...dealF) (cu int, err error) { +func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) { if len(buf) > humanize.MByte*100 { return 0, ErrBufTooLarge } diff --git a/Reply/stream.go b/Reply/stream.go index 802239c..40fdcfe 100644 --- a/Reply/stream.go +++ b/Reply/stream.go @@ -1459,21 +1459,36 @@ func (t *M4SStream) Start() bool { duration := time.Since(startT) //PusherToFile fin genFastSeed - { + if disableFastSeed, ok := ms.common.K_v.LoadV("禁用快速索引生成").(bool); !ok || !disableFastSeed { + type deal interface { + GenFastSeed(reader io.Reader, save func(seedTo time.Duration, cuIndex int64) error) (err error) + } + var dealer deal + switch ms.GetStreamType() { case `mp4`: fmp4Decoder := NewFmp4Decoder() if v, ok := ms.common.K_v.LoadV(`fmp4音视频时间戳容差s`).(float64); ok && v > 0.1 { fmp4Decoder.AVTDiff = v } + dealer = fmp4Decoder + case `flv`: + flvDecoder := NewFlvDecoder() + if v, ok := ms.common.K_v.LoadV(`flv音视频时间戳容差ms`).(float64); ok && v > 100 { + flvDecoder.Diff = v + } + dealer = flvDecoder + default: + } + + if dealer != nil { f := file.New(path, 0, false) if sf, e := replyFunc.VideoFastSeed.InitSav(path + ".fastSeed"); e != nil { l.L(`E: `, e) - } else if e := fmp4Decoder.GenFastSeed(f, sf); e != nil && !errors.Is(e, io.EOF) { + } else if e := dealer.GenFastSeed(f, sf); e != nil && !errors.Is(e, io.EOF) { l.L(`E: `, e) } f.Close() - default: } } diff --git a/Reply/testdata/0.flv b/Reply/testdata/0.flv index f274b63..971ad91 100644 Binary files a/Reply/testdata/0.flv and b/Reply/testdata/0.flv differ diff --git a/demo/config/config_K_v.json b/demo/config/config_K_v.json index 9ded9bc..aceb0c9 100644 --- a/demo/config/config_K_v.json +++ b/demo/config/config_K_v.json @@ -97,6 +97,8 @@ "标题修改检测s-help": "默认900秒,少于默认无效。直播间标题引入审核机制,触发审核时会接收到一个roomchange但标题不变,将持续检测指定时长,如通过审核将修改录播标题", "标题修改检测s": 900, "直播流保存到文件": true, + "禁用快速索引生成-help": "默认false,为false时,在录制结束后读取文件生成快速索引,用于快速响应切片请求", + "禁用快速索引生成": false, "仅保存当前直播间流-help": "启用此项,才会保存Ass", "仅保存当前直播间流": true, "修改标题时重新录制": true,