From 9642c0b9426be14e167cccc379e6b19907c9fa38 Mon Sep 17 00:00:00 2001 From: qydysky Date: Thu, 15 May 2025 20:01:19 +0800 Subject: [PATCH] =?utf8?q?=E5=BC=82=E5=B8=B8=E5=88=86=E9=85=8D=20#205=20(#?= =?utf8?q?206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * 异常分配 #205 * Fix 异常分配 #205 * Fix 异常分配 #205 * Fix 异常分配 #205 --- Reply/fmp4Decode.go | 398 +++++++++++++++++++++++++++++++++++--------- go.mod | 2 +- go.sum | 4 +- 3 files changed, 326 insertions(+), 78 deletions(-) diff --git a/Reply/fmp4Decode.go b/Reply/fmp4Decode.go index eace75d..f45c017 100644 --- a/Reply/fmp4Decode.go +++ b/Reply/fmp4Decode.go @@ -6,7 +6,9 @@ import ( "fmt" "io" "math" + "sync" "time" + "unique" "github.com/dustin/go-humanize" F "github.com/qydysky/bili_danmu/F" @@ -28,28 +30,28 @@ var ( ActionCheckTFail pe.Action = `CheckTFail` ) -var boxs map[string]bool +var boxs map[unique.Handle[string]]bool func init() { - boxs = make(map[string]bool) + boxs = make(map[unique.Handle[string]]bool) //isPureBox? || need to skip? - boxs["ftyp"] = true - boxs["moov"] = false - boxs["mvhd"] = true - boxs["trak"] = false - boxs["tkhd"] = true - boxs["mdia"] = false - boxs["mdhd"] = true - boxs["hdlr"] = true - boxs["minf"] = false || true - boxs["mvex"] = false || true - boxs["moof"] = false - boxs["mfhd"] = true - boxs["traf"] = false - boxs["tfhd"] = true - boxs["tfdt"] = true - boxs["trun"] = true - boxs["mdat"] = true + boxs[unique.Make("ftyp")] = true + boxs[unique.Make("moov")] = false + boxs[unique.Make("mvhd")] = true + boxs[unique.Make("trak")] = false + boxs[unique.Make("tkhd")] = true + boxs[unique.Make("mdia")] = false + boxs[unique.Make("mdhd")] = true + boxs[unique.Make("hdlr")] = true + boxs[unique.Make("minf")] = false || true + boxs[unique.Make("mvex")] = false || true + boxs[unique.Make("moof")] = false + boxs[unique.Make("mfhd")] = true + boxs[unique.Make("traf")] = false + boxs[unique.Make("tfhd")] = true + boxs[unique.Make("tfdt")] = true + boxs[unique.Make("trun")] = true + boxs[unique.Make("mdat")] = true } type ie struct { @@ -89,8 +91,9 @@ func (t *timeStamp) getT() float64 { } type Fmp4Decoder struct { - traks map[int]*trak - buf *slice.Buf[byte] + traks map[int]*trak + buf *slice.Buf[byte] + buflock sync.Mutex AVTDiff float64 // 音视频时间戳容差 Debug bool @@ -168,6 +171,10 @@ func (t *Fmp4Decoder) Search_stream_fmp4(buf []byte, keyframe *slice.Buf[byte]) if len(t.traks) == 0 { return 0, ErrMisTraks } + + t.buflock.Lock() + defer t.buflock.Unlock() + t.buf.Reset() keyframe.Reset() @@ -416,6 +423,10 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) { if len(t.traks) == 0 { return 0, ErrMisTraks } + + t.buflock.Lock() + defer t.buflock.Unlock() + t.buf.Reset() defer func() { @@ -667,14 +678,255 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) { return } +func (t *Fmp4Decoder) oneFNoBuf(buf []byte, w ...dealFMp4) (cu int, err error) { + if len(buf) > humanize.MByte*100 { + return 0, ErrBufTooLarge + } + if len(t.traks) == 0 { + return 0, ErrMisTraks + } + + defer func() { + if err != nil { + cu = 0 + } + }() + + var ( + haveKeyframe bool + // bufModified bool + // maxSequenceNumber int //有时并不是单调增加 + maxVT float64 + maxAT float64 + + //get timeStamp + get_timeStamp = func(tfdt int) (ts timeStamp) { + switch buf[tfdt+8] { + case 0: + ts.data = buf[tfdt+16 : tfdt+20] + ts.timeStamp = int(F.Btoi32(buf, tfdt+16)) + case 1: + ts.data = buf[tfdt+12 : tfdt+20] + ts.timeStamp = int(F.Btoi64(buf, tfdt+12)) + } + return + } + + //get track type + get_track_type = func(tfhd, tfdt int) (ts timeStamp, handlerType byte) { + track, ok := t.traks[int(F.Btoi(buf, tfhd+12, 4))] + if ok { + ts := get_timeStamp(tfdt) + // if track.firstTimeStamp == -1 { + // track.firstTimeStamp = ts.timeStamp + // } + + // ts.firstTimeStamp = track.firstTimeStamp + ts.handlerType = track.handlerType + ts.timescale = track.timescale + + // if ts.timeStamp > track.lastTimeStamp { + // track.lastTimeStamp = ts.timeStamp + // ts.resetTs() + // } + + return ts, track.handlerType + } + return + } + + //is SampleEntries error? + checkSampleEntries = func(trun, mdat int) error { + if buf[trun+11] == 'b' { + for i := trun + 24; i < mdat; i += 12 { + if F.Btoi(buf, i+4, 4) < 1000 { + return errors.New("find sample size less then 1000") + } + } + } + return nil + } + + //is t error? + checkAndSetMaxT = func(ts timeStamp) (err error) { + switch ts.handlerType { + case 'v': + if maxVT == 0 { + maxVT = ts.getT() + } else if maxVT == ts.getT() { + err = ActionCheckTFail.New("equal VT detect") + } else if maxVT > ts.getT() { + err = ActionCheckTFail.New("lower VT detect") + } else { + maxVT = ts.getT() + } + case 'a': + if maxAT == 0 { + maxAT = ts.getT() + } else if maxAT == ts.getT() { + err = ActionCheckTFail.New("equal AT detect") + } else if maxAT > ts.getT() { + err = ActionCheckTFail.New("lower AT detect") + } else { + maxAT = ts.getT() + } + default: + } + return + } + + dropKeyFrame = func(index int) { + haveKeyframe = false + cu = index + } + ) + + ies, recycle, e := decode(buf, "moof") + defer recycle(ies) + if e != nil { + return 0, e + } + + var ErrNormal pe.Action = "ErrNormal" + + err = deals(ies, + []dealIE{ + { + boxNames: []string{"moof", "mfhd", "traf", "tfhd", "tfdt", "trun", "mdat"}, + fs: func(m []ie) error { + var ( + keyframeMoof = buf[m[5].i+20] == byte(0x02) + // moofSN = int(F.Btoi(buf, m[1].i+12, 4)) + video timeStamp + ) + + { + ts, handlerType := get_track_type(m[3].i, m[4].i) + if ts.handlerType == 'v' { + if e := checkSampleEntries(m[5].i, m[6].i); e != nil { + //skip + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + if handlerType == 'v' { + video = ts + } + if e := checkAndSetMaxT(ts); e != nil { + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + + // fmt.Println(ts.getT(), "frame0", keyframeMoof, t.buf.size(), m[0].i, m[6].n, m[6].e) + + //deal frame + if keyframeMoof { + if haveKeyframe && len(w) > 0 { + err = w[0](video.getT(), cu, nil) + dropKeyFrame(m[0].i) + return ErrNormal + } + dropKeyFrame(m[0].i) + haveKeyframe = true + } else if !haveKeyframe { + cu = m[6].e + } + return nil + }, + }, + { + boxNames: []string{"moof", "mfhd", "traf", "tfhd", "tfdt", "trun", "traf", "tfhd", "tfdt", "trun", "mdat"}, + fs: func(m []ie) error { + var ( + keyframeMoof = buf[m[5].i+20] == byte(0x02) || buf[m[9].i+20] == byte(0x02) + // moofSN = int(F.Btoi(buf, m[1].i+12, 4)) + video timeStamp + audio timeStamp + ) + + { + ts, handlerType := get_track_type(m[3].i, m[4].i) + if handlerType == 'v' { + if e := checkSampleEntries(m[5].i, m[6].i); e != nil { + //skip + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + switch handlerType { + case 'v': + video = ts + case 's': + audio = ts + } + if e := checkAndSetMaxT(ts); e != nil { + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + { + ts, handlerType := get_track_type(m[7].i, m[8].i) + if handlerType == 'v' { + if e := checkSampleEntries(m[9].i, m[10].i); e != nil { + //skip + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + switch handlerType { + case 'v': + video = ts + case 's': + audio = ts + } + if e := checkAndSetMaxT(ts); e != nil { + dropKeyFrame(m[0].e) + return pe.Join(ErrDecode, e) + } + } + + //sync audio timeStamp + if t.AVTDiff <= 0.1 { + t.AVTDiff = 0.1 + } + if diff := math.Abs(video.getT() - audio.getT()); diff > t.AVTDiff { + return pe.Join(ErrDecode, fmt.Errorf("时间戳不匹配 %v %v (或许应调整fmp4音视频时间戳容差s>%.2f)", video.timeStamp, audio.timeStamp, diff)) + // copy(video.data, F.Itob64(int64(audio.getT()*float64(video.timescale)))) + } + + //deal frame + if keyframeMoof { + if haveKeyframe && len(w) > 0 { + err = w[0](video.getT(), cu, nil) + dropKeyFrame(m[0].i) + return ErrNormal + } + dropKeyFrame(m[0].i) + haveKeyframe = true + } else if !haveKeyframe { + cu = m[10].e + } + return nil + }, + }, + }, + ) + + if errors.Is(err, ErrNormal) { + err = nil + } + + return +} + // Deprecated: 效率低于GenFastSeed+CutSeed func (t *Fmp4Decoder) Cut(reader io.Reader, startT, duration time.Duration, w io.Writer) (err error) { return t.CutSeed(reader, startT, duration, w, nil, nil) } func (t *Fmp4Decoder) 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.MByte) + buf := make([]byte, humanize.MByte*3) buff := slice.New[byte]() init := false seek := false @@ -699,21 +951,17 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration, fmt.Printf("cut startT: %v duration: %v\n", startT, duration) } 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 - } - err = buff.Append(buf[:n]) - continue + n, e := reader.Read(buf) + if n == 0 && errors.Is(e, io.EOF) { + return io.EOF } + err = buff.Append(buf[:n]) if !init { if frontBuf, e := t.Init_fmp4(buff.GetPureBuf()); e != nil { return pe.New(e.Error(), ActionInitFmp4) } else { if len(frontBuf) == 0 { - bufSize *= 2 continue } else { if t.Debug { @@ -736,13 +984,15 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration, startTM = 0 buff.Clear() } - if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil { - return pe.New(e.Error(), ActionOneFFmp4) - } else { - if dropOffset != 0 { - _ = buff.RemoveFront(dropOffset) + for { + if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil { + return pe.New(e.Error(), ActionOneFFmp4) } else { - bufSize *= 2 + if dropOffset != 0 { + _ = buff.RemoveFront(dropOffset) + } else { + break + } } } } @@ -751,52 +1001,49 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration, } func (t *Fmp4Decoder) GenFastSeed(reader io.Reader, save func(seedTo time.Duration, cuIndex int64) error) (err error) { - bufSize := humanize.KByte * 1100 + t.buflock.Lock() + defer t.buflock.Unlock() + + t.buf.Reset() + totalRead := 0 - buf := make([]byte, humanize.MByte) - buff := slice.New[byte]() + buf := make([]byte, humanize.MByte*3) init := false - over := false firstFT := -1.0 - 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 + for c := 0; err == nil; c++ { + n, e := reader.Read(buf) + if n == 0 && errors.Is(e, io.EOF) { + return io.EOF } - + totalRead += n + err = t.buf.Append(buf[:n]) if !init { - if frontBuf, e := t.Init_fmp4(buff.GetPureBuf()); e != nil { + if frontBuf, e := t.Init_fmp4(t.buf.GetPureBuf()); e != nil { return pe.New(e.Error(), ActionInitFmp4) + } else if len(frontBuf) == 0 { + continue } else { - if len(frontBuf) == 0 { - bufSize *= 2 - continue - } else { - init = true - } + init = true } } else { - if dropOffset, e := t.oneF(buff.GetPureBuf(), func(t float64, index int, buf *slice.Buf[byte]) error { - if firstFT == -1 { - firstFT = t - } - if e := save(time.Second*time.Duration(t-firstFT), int64(totalRead-buff.Size()+index)); e != nil { - return pe.Join(ActionGenFastSeedFmp4, e) - } - return nil - }); e != nil { - return pe.Join(ActionGenFastSeedFmp4, ActionOneFFmp4, e) - } else { - if dropOffset != 0 { - _ = buff.RemoveFront(dropOffset) + for { + if dropOffset, e := t.oneFNoBuf(t.buf.GetPureBuf(), func(ts float64, index int, _ *slice.Buf[byte]) error { + if firstFT == -1 { + firstFT = ts + } + if e := save(time.Second*time.Duration(ts-firstFT), int64(totalRead-t.buf.Size()+index)); e != nil { + return pe.Join(ActionGenFastSeedFmp4, e) + } + return nil + }); e != nil { + return pe.Join(ActionGenFastSeedFmp4, ActionOneFFmp4, e) } else { - bufSize *= 2 + if dropOffset != 0 { + _ = t.buf.RemoveFront(dropOffset) + } else { + break + } } } } @@ -896,10 +1143,11 @@ var ( func searchBox(buf []byte, cu *int) (boxName string, i int, e int, err error) { i = *cu e = i + int(F.Btoi(buf, *cu, fmp4BoxLenSize)) - boxName = string(buf[*cu+fmp4BoxLenSize : *cu+fmp4BoxLenSize+fmp4BoxNameSize]) - isPureBoxOrNeedSkip, ok := boxs[boxName] + boxNameU := unique.Make(string(buf[*cu+fmp4BoxLenSize : *cu+fmp4BoxLenSize+fmp4BoxNameSize])) + boxName = boxNameU.Value() + isPureBoxOrNeedSkip, ok := boxs[boxNameU] if !ok { - err = ErrUnkownBox.WithReason("未知包: " + boxName) + err = ErrUnkownBox.WithReason("未知包: " + boxNameU.Value()) } else if e > len(buf) { err = io.EOF } else if isPureBoxOrNeedSkip { diff --git a/go.mod b/go.mod index 27c39a1..29a2dfa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24 require ( github.com/gotk3/gotk3 v0.6.4 github.com/mdp/qrterminal/v3 v3.2.0 - github.com/qydysky/part v0.28.20250511190336 + github.com/qydysky/part v0.28.20250514163050 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 golang.org/x/text v0.24.0 // indirect diff --git a/go.sum b/go.sum index c2291b6..70d831f 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/qydysky/biliApi v0.0.0-20250509210314-8407d638b123 h1:YXqqrwvzFwfs8Lo github.com/qydysky/biliApi v0.0.0-20250509210314-8407d638b123/go.mod h1:1FbgCj+aOwIvuRRuX/l5uTLb3JIwWyJSa0uEfwpYV/8= github.com/qydysky/brotli v0.0.0-20240828134800-e9913a6e7ed9 h1:k451T+bpsLr+Dq9Ujo+Qtx0iomRA1XXS5ttlEojvfuQ= github.com/qydysky/brotli v0.0.0-20240828134800-e9913a6e7ed9/go.mod h1:cI8/gy/wjy2Eb+p2IUj2ZuDnC8R5Vrx3O0VMPvMvphA= -github.com/qydysky/part v0.28.20250511190336 h1:99Wfw3jbBQ0iohS2bY5xKNh2mwv5weWoh7harkGEOQ0= -github.com/qydysky/part v0.28.20250511190336/go.mod h1:wp71PQdKYcg9jn9yDDvqC4shS/kzejyvFqbfUxuHocY= +github.com/qydysky/part v0.28.20250514163050 h1:BQ7pNQWmXkW/sP2wQchORO3L0cGW6cX8jqvlQVGm5G4= +github.com/qydysky/part v0.28.20250514163050/go.mod h1:wp71PQdKYcg9jn9yDDvqC4shS/kzejyvFqbfUxuHocY= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -- 2.39.2