owpengram-server/internal/app/files/mp4faststart_test.go

205 lines
7.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package files
import (
"bytes"
"encoding/binary"
"testing"
)
// box 构造一个 MP4 box[size(4)][type(4)][payload]。
func box(typ string, payload []byte) []byte {
b := make([]byte, 8+len(payload))
binary.BigEndian.PutUint32(b[0:4], uint32(8+len(payload)))
copy(b[4:8], typ)
copy(b[8:], payload)
return b
}
// stcoBox 构造一个含单个 chunk 偏移的 stco[ver+flags(4)][count(4)=1][offset(4)]。
func stcoBox(offset uint32) []byte {
p := make([]byte, 12)
binary.BigEndian.PutUint32(p[4:8], 1) // entry_count
binary.BigEndian.PutUint32(p[8:12], offset)
return box("stco", p)
}
// buildMoovEndMP4 构造一个 moov 在末尾、stco 指向 mdat 内某偏移的最小合法 MP4。
// 返回 (mp4, mdatPayloadAbsOffset)。
func buildMoovEndMP4(mdatPayload []byte) ([]byte, uint32) {
ftyp := box("ftyp", []byte("isom\x00\x00\x02\x00"))
mdat := box("mdat", mdatPayload)
mdatAbs := uint32(len(ftyp) + 8) // mdat payload 紧跟 mdat 头(8 字节)
// moov→trak→mdia→minf→stbl→stco(offset=mdatAbs)
stbl := box("stbl", stcoBox(mdatAbs))
minf := box("minf", stbl)
mdia := box("mdia", minf)
trak := box("trak", mdia)
moov := box("moov", trak)
out := append([]byte(nil), ftyp...)
out = append(out, mdat...)
out = append(out, moov...)
return out, mdatAbs
}
func TestFaststartMP4MovesMoovAndFixesOffsets(t *testing.T) {
marker := []byte("THE-REAL-CHUNK-DATA-HERE")
// mdat payload前面填充 + markerstco 指向 marker 的绝对偏移。
pad := bytes.Repeat([]byte{0xAB}, 40)
mdatPayload := append(append([]byte(nil), pad...), marker...)
_, mdatAbs := buildMoovEndMP4(mdatPayload)
markerAbs := mdatAbs + uint32(len(pad)) // marker 在原文件里的绝对偏移
// 原 stco 指向 mdatAbsmdat payload 头)。这里把 stco 改成指向 marker 以便断言。
in2, _ := buildMoovEndMP4Marker(mdatPayload, markerAbs)
// 校验前置:原文件里 markerAbs 处确实是 marker。
if !bytes.Equal(in2[markerAbs:markerAbs+uint32(len(marker))], marker) {
t.Fatalf("setup: 原文件 markerAbs 处非 marker")
}
out, changed := faststartMP4(in2)
if !changed {
t.Fatalf("changed = false, want truemoov 在末尾应被搬动)")
}
if len(out) != len(in2) {
t.Fatalf("size 不守恒: in=%d out=%d", len(in2), len(out))
}
// 输出顺序ftyp, moov, mdat。
boxes, ok := parseTopLevelBoxes(out)
if !ok || len(boxes) != 3 || boxes[0].typ != "ftyp" || boxes[1].typ != "moov" || boxes[2].typ != "mdat" {
t.Fatalf("输出顶层顺序错: %+v ok=%v", boxes, ok)
}
// 取出输出 moov 里的 stco 偏移,应指向输出文件里仍是 marker 的位置。
newOff := readSingleStcoOffset(t, out[boxes[1].start:boxes[1].end])
if int(newOff)+len(marker) > len(out) {
t.Fatalf("新偏移越界: %d", newOff)
}
got := out[newOff : int(newOff)+len(marker)]
if !bytes.Equal(got, marker) {
t.Fatalf("新 stco 偏移 %d 指向 %q, want %q偏移修正错误", newOff, got, marker)
}
// 新偏移 = 原偏移 + moovSize。
moovSize := boxes[1].end - boxes[1].start
if int(newOff) != int(markerAbs)+moovSize {
t.Fatalf("新偏移 = %d, want 原 %d + moovSize %d", newOff, markerAbs, moovSize)
}
}
func TestFaststartMP4NoopWhenAlreadyFaststart(t *testing.T) {
// moov 在 mdat 前 → 已 faststart应原样返回。
ftyp := box("ftyp", []byte("isom\x00\x00\x02\x00"))
stbl := box("stbl", stcoBox(100))
moov := box("moov", box("trak", box("mdia", box("minf", stbl))))
mdat := box("mdat", bytes.Repeat([]byte{1}, 64))
in := append(append(append([]byte(nil), ftyp...), moov...), mdat...)
out, changed := faststartMP4(in)
if changed {
t.Fatalf("已 faststart 不应改动")
}
if !bytes.Equal(out, in) {
t.Fatalf("应原样返回")
}
}
func TestInspectMP4LayoutDetectsMoovAtEnd(t *testing.T) {
in, _ := buildMoovEndMP4(bytes.Repeat([]byte{0xCD}, 100))
readAt := func(off, n int64) ([]byte, error) {
if off+n > int64(len(in)) {
n = int64(len(in)) - off
}
return in[off : off+n], nil
}
l, ok := inspectMP4Layout(int64(len(in)), readAt)
if !ok {
t.Fatalf("inspect 失败")
}
if !l.needsFaststart {
t.Fatalf("moov 在末尾应 needsFaststart=true")
}
if !l.moovIsLast {
t.Fatalf("moov 是最后一个 box 应 moovIsLast=true")
}
if l.ftypStart != 0 || l.moovEnd != int64(len(in)) {
t.Fatalf("range 不对: %+v (len=%d)", l, len(in))
}
}
func TestInspectMP4LayoutNoFaststartWhenMoovFirst(t *testing.T) {
ftyp := box("ftyp", []byte("isom\x00\x00\x02\x00"))
moov := box("moov", box("trak", box("mdia", box("minf", box("stbl", stcoBox(100))))))
mdat := box("mdat", bytes.Repeat([]byte{1}, 100))
in := append(append(append([]byte(nil), ftyp...), moov...), mdat...)
readAt := func(off, n int64) ([]byte, error) {
if off+n > int64(len(in)) {
n = int64(len(in)) - off
}
return in[off : off+n], nil
}
l, ok := inspectMP4Layout(int64(len(in)), readAt)
if !ok || l.needsFaststart {
t.Fatalf("moov 在前应 needsFaststart=false, got ok=%v %+v", ok, l)
}
}
// 流式拼接(ftyp + patched moov + 中段)必须与全量 faststartMP4 输出逐字节一致。
func TestStreamingAssemblyMatchesFullRewrite(t *testing.T) {
in, _ := buildMoovEndMP4(bytes.Repeat([]byte{0xEE}, 256))
readAt := func(off, n int64) ([]byte, error) { return in[off : off+n], nil }
l, ok := inspectMP4Layout(int64(len(in)), readAt)
if !ok || !l.needsFaststart || !l.moovIsLast {
t.Fatalf("setup: %+v ok=%v", l, ok)
}
// 流式版本ftyp + patched moov + in[ftypEnd:moovStart]
ftyp := append([]byte(nil), in[l.ftypStart:l.ftypEnd]...)
moov := append([]byte(nil), in[l.moovStart:l.moovEnd]...)
if !patchChunkOffsets(moov, l.moovEnd-l.moovStart) {
t.Fatalf("patch 失败")
}
var streaming []byte
streaming = append(streaming, ftyp...)
streaming = append(streaming, moov...)
streaming = append(streaming, in[l.ftypEnd:l.moovStart]...)
full, changed := faststartMP4(in)
if !changed {
t.Fatalf("full 应 changed")
}
if !bytes.Equal(streaming, full) {
t.Fatalf("流式拼接与全量重写不一致: streaming=%d full=%d", len(streaming), len(full))
}
}
func TestFaststartMP4NoopOnNonMP4(t *testing.T) {
for _, data := range [][]byte{
nil,
[]byte("not an mp4 at all"),
{0, 0, 0, 4}, // size 太小
} {
if out, changed := faststartMP4(data); changed || !bytes.Equal(out, data) {
t.Fatalf("非 MP4 应原样不动: changed=%v", changed)
}
}
}
// buildMoovEndMP4Marker 同 buildMoovEndMP4但 stco 指向给定绝对偏移。
func buildMoovEndMP4Marker(mdatPayload []byte, stcoOffset uint32) ([]byte, uint32) {
ftyp := box("ftyp", []byte("isom\x00\x00\x02\x00"))
mdat := box("mdat", mdatPayload)
mdatAbs := uint32(len(ftyp) + 8)
stbl := box("stbl", stcoBox(stcoOffset))
moov := box("moov", box("trak", box("mdia", box("minf", stbl))))
out := append([]byte(nil), ftyp...)
out = append(out, mdat...)
out = append(out, moov...)
return out, mdatAbs
}
func readSingleStcoOffset(t *testing.T, moov []byte) uint32 {
t.Helper()
idx := bytes.Index(moov, []byte("stco"))
if idx < 0 {
t.Fatalf("moov 里找不到 stco")
}
// stco 头后type(4) 已在 idxpayload 从 idx+4 起 = ver+flags(4)+count(4)+offset(4)
off := idx + 4 + 4 + 4
return binary.BigEndian.Uint32(moov[off : off+4])
}