]> 127.0.0.1 Git - bili_danmu/.git/commitdiff
Add flv快速索引文件 (#147)
authorqydysky <qydysky@foxmail.com>
Mon, 13 Jan 2025 14:14:38 +0000 (22:14 +0800)
committerGitHub <noreply@github.com>
Mon, 13 Jan 2025 14:14:38 +0000 (22:14 +0800)
* Add flv快速索引文件

* Fix flv示例

* Improve workflow

.github/workflows/test.yml
README.md
Reply/F.go
Reply/emots/README.md [new file with mode: 0644]
Reply/flvDecode.go
Reply/flvDecode_test.go
Reply/fmp4Decode.go
Reply/stream.go
Reply/testdata/0.flv
demo/config/config_K_v.json

index 8fa5a2770d4e521784198cb829716eaa4564a949..55c21f8092b41c0a9e88fdc95e4297fce5a11151 100644 (file)
@@ -9,6 +9,8 @@ on:
       - '**.go'
       - '**.mod'
       - '**.sum'
+      - '**.flv'
+      - '**.mp4'
 
 jobs:
   buildtest:
index dde3534a24ae6f81f219252e24734d7b460f0119..77720da53a4fe5122c4bf7fa1d1fc43b686c90f6 100644 (file)
--- 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)
+æ·»å\8a å¿«é\80\9fç´¢å¼\95æ\96\87ä»¶ç\94\9fæ\88\90ï¼\8cå°\86å\9c¨å½\95å\88¶å®\8cæ\88\90å\90\8eï¼\8cè\87ªå®\9aä¹\89å\9b\9eè°\83å\91½ä»¤æ\89§è¡\8cå\89\8dï¼\8c读å\8f\96è§\86é¢\91æ\96\87ä»¶ï¼\8cå¹¶å°\86å\85³é\94®å¸§ç\9a\84æ\97¶é\97´æ\88³å\92\8c对åº\94ç\9a\84ä¸\8bæ \87å\80¼è®°å½\95å\9c¨`.fastSeed`æ\96\87ä»¶ï¼\8cç\94¨äº\8eå\8a å¿«å\90\8eç»­å\88\87ç\89\87请æ±\82å\93\8dåº\94ã\80\82(>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`文件格式:
index d5547bd0e8c40307a4174c701e8dd8a37b4b1c09..786cddecb35bec1240b3640b90e38be1bd01b9c8 100644 (file)
@@ -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 (file)
index 0000000..e69de29
index b78880ca7746bc4008b7d112a13091a68da8d333..068e32f71ecc2451e007f82f08c6ca496abc149f 100644 (file)
@@ -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 {
index 109cd71cdd1e3b7a4b99b30e2ee408ec81e8696f..fb4995115bd41323849e100ddccbce7602a227b6 100644 (file)
@@ -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)
        }
index 8c5eccdde85f22126545cddf52439ff2c19b5a0c..a2bbf897b9e1ef0d7332e6d15e8602220d40bcac 100644 (file)
@@ -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
        }
index 802239c1c3f4bf261227628680eeb8ee7a1dca0b..40fdcfeddcace26c688af11ae64e3cd6d234327e 100644 (file)
@@ -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:
                                                }
                                        }
 
index f274b63356c255a7f8d0e9713affb5a80b743f9a..971ad91c5b0074f4784eaf069d4bb30625552265 100644 (file)
Binary files a/Reply/testdata/0.flv and b/Reply/testdata/0.flv differ
index 9ded9bc7ba26c2fd389b193f173052d957ccd383..aceb0c97e59ffee87efba4a4dbfcf65cd7c72548 100644 (file)
@@ -97,6 +97,8 @@
     "标题修改检测s-help": "默认900秒,少于默认无效。直播间标题引入审核机制,触发审核时会接收到一个roomchange但标题不变,将持续检测指定时长,如通过审核将修改录播标题",
     "标题修改检测s": 900,
     "直播流保存到文件": true,
+    "禁用快速索引生成-help": "默认false,为false时,在录制结束后读取文件生成快速索引,用于快速响应切片请求",
+    "禁用快速索引生成": false,
     "仅保存当前直播间流-help": "启用此项,才会保存Ass",
     "仅保存当前直播间流": true,
     "修改标题时重新录制": true,