Files
djk/internal/app/playback_remux.go

202 lines
4.7 KiB
Go

package app
import (
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
)
type playbackRemuxKey struct{}
type playbackMP4Box struct {
kind string
body []byte
}
const playbackInitLimit = 256 * 1024
func readPlaybackMP4Init(reader io.Reader) ([]byte, string, error) {
var initial []byte
for len(initial) < playbackInitLimit {
header := make([]byte, 8)
if _, err := io.ReadFull(reader, header); err != nil {
return nil, "", err
}
size := uint64(binary.BigEndian.Uint32(header))
kind := string(header[4:8])
if size == 1 {
extended := make([]byte, 8)
if _, err := io.ReadFull(reader, extended); err != nil {
return nil, "", err
}
size = binary.BigEndian.Uint64(extended)
header = append(header, extended...)
}
if size < uint64(len(header)) || size > uint64(playbackInitLimit-len(initial)) || kind != "ftyp" && kind != "free" && kind != "moov" {
return nil, "", errors.New("播放初始化数据无效或过大")
}
body := make([]byte, int(size)-len(header))
if _, err := io.ReadFull(reader, body); err != nil {
return nil, "", err
}
initial = append(initial, header...)
initial = append(initial, body...)
if kind == "moov" {
return initial, playbackRemuxMIME(body), nil
}
}
return nil, "", errors.New("播放初始化数据过大")
}
func playbackMP4Boxes(data []byte) ([]playbackMP4Box, bool) {
var boxes []playbackMP4Box
for len(data) > 0 {
if len(data) < 8 {
return nil, false
}
size, header := uint64(binary.BigEndian.Uint32(data)), 8
if size == 1 {
if len(data) < 16 {
return nil, false
}
size, header = binary.BigEndian.Uint64(data[8:16]), 16
}
if size < uint64(header) || size > uint64(len(data)) {
return nil, false
}
boxes = append(boxes, playbackMP4Box{kind: string(data[4:8]), body: data[header:int(size)]})
data = data[int(size):]
}
return boxes, true
}
func playbackMP4Child(data []byte, path ...string) []byte {
for _, kind := range path {
boxes, valid := playbackMP4Boxes(data)
if !valid {
return nil
}
data = nil
for _, box := range boxes {
if box.kind == kind {
if data != nil {
return nil
}
data = box.body
}
}
if data == nil {
return nil
}
}
return data
}
func playbackRemuxMIME(moov []byte) string {
boxes, valid := playbackMP4Boxes(moov)
if !valid {
return ""
}
var video string
audio := false
for _, track := range boxes {
if track.kind != "trak" {
continue
}
stsd := playbackMP4Child(track.body, "mdia", "minf", "stbl", "stsd")
if len(stsd) < 8 || binary.BigEndian.Uint32(stsd[4:8]) != 1 {
return ""
}
entries, valid := playbackMP4Boxes(stsd[8:])
if !valid || len(entries) != 1 {
return ""
}
entry := entries[0]
switch entry.kind {
case "avc1":
if video != "" || len(entry.body) < 78 {
return ""
}
width, height := int(binary.BigEndian.Uint16(entry.body[24:26])), int(binary.BigEndian.Uint16(entry.body[26:28]))
if width == 0 || height == 0 || width > 1920 || height > 1920 || width*height > 1920*1080 {
return ""
}
avcc := playbackMP4Child(entry.body[78:], "avcC")
if len(avcc) < 7 || avcc[0] != 1 || avcc[1] != 66 && avcc[1] != 77 && avcc[1] != 100 || avcc[3] == 0 || avcc[3] > 42 {
return ""
}
video = fmt.Sprintf("avc1.%02X%02X%02X", avcc[1], avcc[2], avcc[3])
case "mp4a":
if audio || len(entry.body) < 28 || binary.BigEndian.Uint16(entry.body[8:10]) != 0 {
return ""
}
channels := binary.BigEndian.Uint16(entry.body[16:18])
if channels < 1 || channels > 2 || !playbackAACLC(playbackMP4Child(entry.body[28:], "esds")) {
return ""
}
audio = true
default:
return ""
}
}
if video == "" {
return ""
}
codecs := []string{video}
if audio {
codecs = append(codecs, "mp4a.40.2")
}
return `video/mp4; codecs="` + strings.Join(codecs, ", ") + `"`
}
func playbackMP4Descriptor(data []byte, tag byte) []byte {
if len(data) < 2 || data[0] != tag {
return nil
}
length := 0
for i := 1; i < len(data) && i <= 4; i++ {
length = length<<7 | int(data[i]&0x7f)
if data[i]&0x80 == 0 {
if length > len(data)-i-1 {
return nil
}
return data[i+1 : i+1+length]
}
}
return nil
}
func playbackAACLC(esds []byte) bool {
if len(esds) < 4 {
return false
}
es := playbackMP4Descriptor(esds[4:], 3)
if len(es) < 3 {
return false
}
flags, offset := es[2], 3
if flags&0x80 != 0 {
offset += 2
}
if flags&0x40 != 0 {
if offset >= len(es) {
return false
}
offset += 1 + int(es[offset])
}
if flags&0x20 != 0 {
offset += 2
}
if offset >= len(es) {
return false
}
decoder := playbackMP4Descriptor(es[offset:], 4)
if len(decoder) < 13 || decoder[0] != 0x40 || decoder[1]>>2 != 5 {
return false
}
config := playbackMP4Descriptor(decoder[13:], 5)
return len(config) >= 2 && config[0]>>3 == 2
}