test hls ll
This commit is contained in:
98
format/fmp4/esio/builder.go
Normal file
98
format/fmp4/esio/builder.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package esio
|
||||
|
||||
type builder struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (b *builder) Bytes() []byte {
|
||||
return b.buf
|
||||
}
|
||||
|
||||
// Grow the buffer by n bytes and return a slice holding the new area.
|
||||
// The slice is only valid until the next method called on the builder.
|
||||
func (b *builder) Grow(n int) []byte {
|
||||
pos := len(b.buf)
|
||||
b.buf = append(b.buf, make([]byte, n)...)
|
||||
return b.buf[pos:]
|
||||
}
|
||||
|
||||
// WriteByte appends a uint8
|
||||
func (b *builder) WriteByte(v byte) error {
|
||||
b.buf = append(b.buf, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteU16 appends a 16-but unsigned big-endian integer
|
||||
func (b *builder) WriteU16(v uint16) {
|
||||
b.buf = append(b.buf, uint8(v>>8), uint8(v))
|
||||
}
|
||||
|
||||
// WriteU24 appends a 24-bit unsigned big-endian integer
|
||||
func (b *builder) WriteU24(v uint32) {
|
||||
b.buf = append(b.buf, uint8(v>>16), uint8(v>>8), uint8(v))
|
||||
}
|
||||
|
||||
// WriteU32 appends a 32-bit unsigned big-endian integer
|
||||
func (b *builder) WriteU32(v uint32) {
|
||||
b.buf = append(b.buf, uint8(v>>24), uint8(v>>16), uint8(v>>8), uint8(v))
|
||||
}
|
||||
|
||||
// WriteU64 appends a 64-bit unsigned big-endian integer
|
||||
func (b *builder) WriteU64(v uint64) {
|
||||
b.buf = append(b.buf,
|
||||
uint8(v>>56),
|
||||
uint8(v>>48),
|
||||
uint8(v>>40),
|
||||
uint8(v>>32),
|
||||
uint8(v>>24),
|
||||
uint8(v>>16),
|
||||
uint8(v>>8),
|
||||
uint8(v),
|
||||
)
|
||||
}
|
||||
|
||||
// Write appends a slice. It never returns an error, but implements io.Writer
|
||||
func (b *builder) Write(d []byte) (int, error) {
|
||||
b.buf = append(b.buf, d...)
|
||||
return len(d), nil
|
||||
}
|
||||
|
||||
// Cursor allocates length bytes and returns a pointer that can be used to access the allocated region later, even after the buffer has grown
|
||||
func (b *builder) Cursor(length int) cursor {
|
||||
c := cursor{builder: b, i: len(b.buf)}
|
||||
b.Grow(length)
|
||||
c.j = len(b.buf)
|
||||
return c
|
||||
}
|
||||
|
||||
// Descriptor writes a descriptor tag and leaves room for a length later.
|
||||
// Call DescriptorDone on the returned cursor to complete it.
|
||||
func (b *builder) Descriptor(tag Tag) cursor {
|
||||
b.WriteByte(byte(tag))
|
||||
return b.Cursor(4)
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
builder *builder
|
||||
i, j int
|
||||
}
|
||||
|
||||
func (c cursor) Bytes() []byte {
|
||||
return c.builder.buf[c.i:c.j]
|
||||
}
|
||||
|
||||
// DescriptorDone completes a descriptor tag by writing the length of its contents.
|
||||
// Either pass the length of the contents, or -1 if the current end of the buffer is the end of the contents.
|
||||
func (c cursor) DescriptorDone(length int) {
|
||||
if length < 0 {
|
||||
length = len(c.builder.buf) - c.j
|
||||
}
|
||||
buf := c.Bytes()
|
||||
for i := 3; i >= 0; i-- {
|
||||
v := byte(length >> uint(7*i) & 0x7f)
|
||||
if i != 0 {
|
||||
v |= 0x80
|
||||
}
|
||||
buf[3-i] = v
|
||||
}
|
||||
}
|
||||
95
format/fmp4/esio/decoderconf.go
Normal file
95
format/fmp4/esio/decoderconf.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package esio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type DecoderConfigDescriptor struct {
|
||||
ObjectType ObjectType
|
||||
StreamType StreamType
|
||||
BufferSize uint32
|
||||
MaxBitrate uint32
|
||||
AvgBitrate uint32
|
||||
|
||||
AudioSpecific []byte
|
||||
}
|
||||
|
||||
type ObjectType uint8
|
||||
|
||||
// ISO/IEC 14496-1 7.2.6.6.2 Table 5
|
||||
const ObjectTypeAudio = ObjectType(0x40)
|
||||
|
||||
type StreamType uint8
|
||||
|
||||
// ISO/IEC 14496-1 7.2.6.6.2 Table 6
|
||||
const StreamTypeAudioStream = StreamType(0x05)
|
||||
|
||||
func parseDecoderConfig(d []byte) (*DecoderConfigDescriptor, error) {
|
||||
if len(d) < 13 {
|
||||
return nil, errors.New("DecoderConfigDescriptor short")
|
||||
}
|
||||
conf := &DecoderConfigDescriptor{
|
||||
ObjectType: ObjectType(d[0]),
|
||||
StreamType: StreamType(d[1] >> 2),
|
||||
BufferSize: pio.U24BE(d[2:]),
|
||||
MaxBitrate: pio.U32BE(d[5:]),
|
||||
AvgBitrate: pio.U32BE(d[9:]),
|
||||
}
|
||||
d = d[13:]
|
||||
for len(d) > 0 {
|
||||
tag, contents, remainder, err := parseHeader(d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DecoderConfigDescriptor: %w", err)
|
||||
}
|
||||
d = remainder
|
||||
switch tag {
|
||||
case TagDecoderSpecificInfo:
|
||||
switch conf.ObjectType {
|
||||
case ObjectTypeAudio:
|
||||
conf.AudioSpecific = contents
|
||||
}
|
||||
}
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (c *DecoderConfigDescriptor) appendTo(b *builder) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
cursor := b.Descriptor(TagDecoderConfigDescriptor)
|
||||
defer cursor.DescriptorDone(-1)
|
||||
b.WriteByte(byte(c.ObjectType))
|
||||
b.WriteByte(byte(c.StreamType<<2) | 1)
|
||||
b.WriteU24(c.BufferSize)
|
||||
b.WriteU32(c.MaxBitrate)
|
||||
b.WriteU32(c.AvgBitrate)
|
||||
switch {
|
||||
case c.AudioSpecific != nil:
|
||||
// ISO/IEC 14496-3
|
||||
// 1.6.2.1 - base AudioSpecificConfig
|
||||
// 4.4.1 - GASpecificConfig
|
||||
// but we don't actually need to inspect this right now so just preserve the bytes
|
||||
c2 := b.Descriptor(TagDecoderSpecificInfo)
|
||||
b.Write(c.AudioSpecific)
|
||||
c2.DescriptorDone(-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecoderConfigFromCodecData(stream av.CodecData) (*DecoderConfigDescriptor, error) {
|
||||
switch cd := stream.(type) {
|
||||
case aacparser.CodecData:
|
||||
return &DecoderConfigDescriptor{
|
||||
ObjectType: ObjectTypeAudio,
|
||||
StreamType: StreamTypeAudioStream,
|
||||
AudioSpecific: cd.MPEG4AudioConfigBytes(),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("can't marshal %T to DecoderConfigDescriptor", stream)
|
||||
}
|
||||
162
format/fmp4/esio/esio.go
Normal file
162
format/fmp4/esio/esio.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package esio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type StreamDescriptor struct {
|
||||
ESID uint16
|
||||
DependsOn *uint16
|
||||
URL *string
|
||||
OCR *uint16
|
||||
|
||||
DecoderConfig *DecoderConfigDescriptor
|
||||
SLConfig *SLConfigDescriptor
|
||||
}
|
||||
|
||||
// Tag identifies element stream descriptor types
|
||||
type Tag uint8
|
||||
|
||||
// ISO/IEC 14496-1:2004 7.2.2 Table 1
|
||||
const (
|
||||
TagForbidden = Tag(iota)
|
||||
TagObjectDescriptor
|
||||
TagInitialObjectDescriptor
|
||||
TagESDescriptor
|
||||
TagDecoderConfigDescriptor
|
||||
TagDecoderSpecificInfo
|
||||
TagSLConfigDescriptor
|
||||
)
|
||||
|
||||
const (
|
||||
esFlagStreamDependence = 0x80
|
||||
esFlagURL = 0x40
|
||||
esFlagOCR = 0x20
|
||||
)
|
||||
|
||||
func ParseStreamDescriptor(start []byte) (desc *StreamDescriptor, remainder []byte, err error) {
|
||||
// ISO/IEC 14496-1:2004 7.2.6.5.1
|
||||
tag, d, remainder, err := parseHeader(start)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ES_Descriptor: %w", err)
|
||||
return
|
||||
} else if tag != TagESDescriptor {
|
||||
err = fmt.Errorf("expected ES_Descriptor but got tag %02X", tag)
|
||||
return
|
||||
}
|
||||
desc = &StreamDescriptor{ESID: pio.U16BE(d)}
|
||||
flags := d[2]
|
||||
d = d[3:]
|
||||
if flags&esFlagStreamDependence != 0 {
|
||||
v := pio.U16BE(d)
|
||||
desc.DependsOn = &v
|
||||
d = d[2:]
|
||||
}
|
||||
if flags&esFlagURL != 0 {
|
||||
urlLength := d[0]
|
||||
v := string(d[1 : 1+urlLength])
|
||||
desc.URL = &v
|
||||
d = d[1+urlLength:]
|
||||
}
|
||||
if flags&esFlagOCR != 0 {
|
||||
v := pio.U16BE(d)
|
||||
desc.OCR = &v
|
||||
d = d[2:]
|
||||
}
|
||||
for len(d) > 0 {
|
||||
var child []byte
|
||||
tag, child, d, err = parseHeader(d)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ES_Descriptor: %w", err)
|
||||
return
|
||||
}
|
||||
switch tag {
|
||||
case TagDecoderConfigDescriptor:
|
||||
desc.DecoderConfig, err = parseDecoderConfig(child)
|
||||
case TagSLConfigDescriptor:
|
||||
desc.SLConfig, err = parseSLConfig(child)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
remainder = d
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StreamDescriptor) Marshal() ([]byte, error) {
|
||||
var b builder
|
||||
cursor := b.Descriptor(TagESDescriptor)
|
||||
b.WriteU16(s.ESID)
|
||||
var flags uint8
|
||||
if s.DependsOn != nil {
|
||||
flags |= esFlagStreamDependence
|
||||
}
|
||||
if s.URL != nil {
|
||||
flags |= esFlagURL
|
||||
}
|
||||
if s.OCR != nil {
|
||||
flags |= esFlagOCR
|
||||
}
|
||||
b.WriteByte(flags)
|
||||
if s.DependsOn != nil {
|
||||
b.WriteU16(*s.DependsOn)
|
||||
}
|
||||
if s.URL != nil {
|
||||
b.WriteByte(byte(len(*s.URL)))
|
||||
b.Write([]byte(*s.URL))
|
||||
}
|
||||
if s.OCR != nil {
|
||||
b.WriteU16(*s.OCR)
|
||||
}
|
||||
if err := s.DecoderConfig.appendTo(&b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.SLConfig.appendTo(&b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cursor.DescriptorDone(-1)
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func parseLength(start []byte) (length int, d []byte, err error) {
|
||||
// ISO/IEC 14496-1:2004 8.3.3
|
||||
d = start
|
||||
for i := 0; i < 4; i++ {
|
||||
if len(d) == 0 {
|
||||
err = errors.New("short tag")
|
||||
return
|
||||
}
|
||||
v := d[0]
|
||||
d = d[1:]
|
||||
length <<= 7
|
||||
length |= int(v & 0x7f)
|
||||
if v&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseHeader(start []byte) (tag Tag, contents, d []byte, err error) {
|
||||
d = start
|
||||
if len(d) < 2 {
|
||||
err = errors.New("short tag")
|
||||
return
|
||||
}
|
||||
tag = Tag(d[0])
|
||||
length, d, err := parseLength(d[1:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if length > len(d) {
|
||||
err = fmt.Errorf("short tag: %02x: expected %d bytes but only got %d", tag, length, len(d))
|
||||
return
|
||||
}
|
||||
contents = d[:length]
|
||||
d = d[length:]
|
||||
return
|
||||
}
|
||||
43
format/fmp4/esio/slconf.go
Normal file
43
format/fmp4/esio/slconf.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package esio
|
||||
|
||||
import "errors"
|
||||
|
||||
type SLConfigDescriptor struct {
|
||||
Predefined SLConfigPredefined
|
||||
Custom []byte
|
||||
}
|
||||
|
||||
// SLConfigPredefined references a standard SL config by index
|
||||
type SLConfigPredefined uint8
|
||||
|
||||
// ISO/IEC 14496-1:2004 7.3.2.3.2 Table 12
|
||||
const (
|
||||
SLConfigCustom = SLConfigPredefined(iota)
|
||||
SLConfigNull
|
||||
SLConfigMP4
|
||||
)
|
||||
|
||||
func parseSLConfig(d []byte) (*SLConfigDescriptor, error) {
|
||||
// ISO/IEC 14496-1:2004 7.3.2.3
|
||||
if len(d) == 0 {
|
||||
return nil, errors.New("SLConfigDescriptor short")
|
||||
}
|
||||
sl := &SLConfigDescriptor{Predefined: SLConfigPredefined(d[0])}
|
||||
if sl.Predefined == SLConfigCustom {
|
||||
sl.Custom = d[1:]
|
||||
}
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
func (c *SLConfigDescriptor) appendTo(b *builder) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
cursor := b.Descriptor(TagSLConfigDescriptor)
|
||||
defer cursor.DescriptorDone(-1)
|
||||
b.WriteByte(byte(c.Predefined))
|
||||
if c.Predefined == SLConfigCustom {
|
||||
b.Write(c.Custom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user