package files import "encoding/binary" // faststartMP4 把 MP4 的 moov 原子移到 mdat 之前(faststart),并把 moov 内所有 // stco/co64 chunk 偏移整体加上 moov 的大小(因为 moov 插到 mdat 前会把媒体数据整体后移)。 // 返回 (newData, changed)。非 MP4 / 已 faststart / 任何解析异常时返回 (data, false) 原样, // 绝不破坏数据——这是上传热路径,宁可不优化也不能把好视频改坏。 // // 背景:TDesktop 的 story 流式播放路径无法处理 moov 在文件末尾的视频(av_read_frame // 报 Invalid data),普通 Telegram 客户端上传前会 faststart。telesrv 在落盘前补这一步, // 让 supports_streaming=true 的承诺对所有客户端成立。不转码、保留原编码(含 HEVC)。 func faststartMP4(data []byte) ([]byte, bool) { boxes, ok := parseTopLevelBoxes(data) if !ok { return data, false } ftypIdx, moovIdx, firstMdatIdx := -1, -1, -1 for i, b := range boxes { switch b.typ { case "ftyp": if ftypIdx < 0 { ftypIdx = i } case "moov": if moovIdx < 0 { moovIdx = i } case "mdat": if firstMdatIdx < 0 { firstMdatIdx = i } } } // 必须有 ftyp(且在最前)、moov、mdat;moov 已在 mdat 前则已 faststart。 if ftypIdx != 0 || moovIdx < 0 || firstMdatIdx < 0 { return data, false } if moovIdx < firstMdatIdx { return data, false } // 拷出 moov(独立底层数组,后续就地改偏移不影响原 data)。 moov := append([]byte(nil), data[boxes[moovIdx].start:boxes[moovIdx].end]...) moovSize := int64(len(moov)) if !patchChunkOffsets(moov, moovSize) { return data, false } // 重组:ftyp + moov + 其余 box(除 ftyp/moov)按原顺序。 out := make([]byte, 0, len(data)) out = append(out, data[boxes[ftypIdx].start:boxes[ftypIdx].end]...) out = append(out, moov...) for i, b := range boxes { if i == ftypIdx || i == moovIdx { continue } out = append(out, data[b.start:b.end]...) } if len(out) != len(data) { // 长度必须守恒(只搬不改大小);不守恒说明哪里算错,保守放弃。 return data, false } return out, true } // mp4Layout 是只读 box 头得出的顶层结构信息,用于在不读取整段媒体的前提下判断是否需要 // faststart 以及如何流式重写。 type mp4Layout struct { needsFaststart bool // moov 在 mdat 之后 moovIsLast bool // moov 是最后一个顶层 box(可走流式重写) ftypStart, ftypEnd int64 moovStart, moovEnd int64 } // inspectMP4Layout 只读顶层 box 头(每个 ≤16 字节,跳过 box 负载)来判定结构,避免为了 // 「检查是否已 faststart」而把整段视频读进内存。readAt(off, n) 读取 [off, off+n) 字节。 // 非 MP4 / 结构异常返回 (·, false)。 func inspectMP4Layout(size int64, readAt func(off, n int64) ([]byte, error)) (mp4Layout, bool) { l := mp4Layout{moovStart: -1, moovEnd: -1} mdatStart := int64(-1) ftypSeen := false p := int64(0) for boxes := 0; p+8 <= size; boxes++ { if boxes > 1024 { // 顶层 box 数量上限,防御异常文件 return mp4Layout{}, false } hdr, err := readAt(p, 16) if err != nil || int64(len(hdr)) < 8 { return mp4Layout{}, false } boxSize := int64(binary.BigEndian.Uint32(hdr[0:4])) typ := string(hdr[4:8]) switch { case boxSize == 1: if len(hdr) < 16 { return mp4Layout{}, false } boxSize = int64(binary.BigEndian.Uint64(hdr[8:16])) case boxSize == 0: boxSize = size - p } if boxSize < 8 || p+boxSize > size { return mp4Layout{}, false } switch typ { case "ftyp": if p != 0 { return mp4Layout{}, false // ftyp 必须在最前 } ftypSeen = true l.ftypStart, l.ftypEnd = p, p+boxSize case "moov": if l.moovStart < 0 { l.moovStart, l.moovEnd = p, p+boxSize } case "mdat": if mdatStart < 0 { mdatStart = p } } p += boxSize } if p != size || !ftypSeen || l.moovStart < 0 || mdatStart < 0 { return mp4Layout{}, false } l.needsFaststart = l.moovStart > mdatStart l.moovIsLast = l.moovEnd == size return l, true } type boxRef struct { typ string start, end int } // parseTopLevelBoxes 顺序解析顶层 box,要求恰好无缝覆盖整个 data,否则视为非法不处理。 func parseTopLevelBoxes(data []byte) ([]boxRef, bool) { var boxes []boxRef p := 0 for p+8 <= len(data) { size := int(binary.BigEndian.Uint32(data[p : p+4])) typ := string(data[p+4 : p+8]) switch { case size == 1: if p+16 > len(data) { return nil, false } size64 := binary.BigEndian.Uint64(data[p+8 : p+16]) size = int(size64) case size == 0: size = len(data) - p } if size < 8 || p+size > len(data) { return nil, false } boxes = append(boxes, boxRef{typ: typ, start: p, end: p + size}) p += size } if p != len(data) { return nil, false } return boxes, true } // patchChunkOffsets 递归进入 moov 的容器 box,把 stco/co64 的每个偏移 += delta。 func patchChunkOffsets(box []byte, delta int64) bool { if len(box) < 8 { return false } p := 8 // 跳过自身 box 头(moov 用 32 位 size,极少 64 位;若 64 位则下面 walk 仍从 8 起会错→由调用方 moov 头守恒保证) for p+8 <= len(box) { size := int(binary.BigEndian.Uint32(box[p : p+4])) typ := string(box[p+4 : p+8]) hdr := 8 switch { case size == 1: if p+16 > len(box) { return false } size = int(binary.BigEndian.Uint64(box[p+8 : p+16])) hdr = 16 case size == 0: size = len(box) - p } if size < hdr || p+size > len(box) { return false } child := box[p : p+size] switch typ { case "stco": if !patchStco(child, delta) { return false } case "co64": if !patchCo64(child, delta) { return false } case "trak", "mdia", "minf", "stbl", "edts": if !patchChunkOffsets(child, delta) { return false } } p += size } return true } // patchStco:stco = [size(4)][type(4)][version+flags(4)][entry_count(4)][offsets 4*count]。 func patchStco(box []byte, delta int64) bool { if len(box) < 16 { return false } count := binary.BigEndian.Uint32(box[12:16]) off := 16 if int64(off)+int64(count)*4 > int64(len(box)) { return false } for i := uint32(0); i < count; i++ { v := binary.BigEndian.Uint32(box[off : off+4]) binary.BigEndian.PutUint32(box[off:off+4], uint32(int64(v)+delta)) off += 4 } return true } // patchCo64:co64 entries 为 8 字节。 func patchCo64(box []byte, delta int64) bool { if len(box) < 16 { return false } count := binary.BigEndian.Uint32(box[12:16]) off := 16 if int64(off)+int64(count)*8 > int64(len(box)) { return false } for i := uint32(0); i < count; i++ { v := binary.BigEndian.Uint64(box[off : off+8]) binary.BigEndian.PutUint64(box[off:off+8], v+uint64(delta)) off += 8 } return true }