初始提交:短剧库源码(含转码优化 ultrafast)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user