first commit
This commit is contained in:
124
format/aac/aac.go
Normal file
124
format/aac/aac.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/av/avutil"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
)
|
||||
|
||||
type Muxer struct {
|
||||
w io.Writer
|
||||
config aacparser.MPEG4AudioConfig
|
||||
adtshdr []byte
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return &Muxer{
|
||||
adtshdr: make([]byte, aacparser.ADTSHeaderLength),
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
if len(streams) > 1 || streams[0].Type() != av.AAC {
|
||||
err = fmt.Errorf("aac: must be only one aac stream")
|
||||
return
|
||||
}
|
||||
self.config = streams[0].(aacparser.CodecData).Config
|
||||
if self.config.ObjectType > aacparser.AOT_AAC_LTP {
|
||||
err = fmt.Errorf("aac: AOT %d is not allowed in ADTS", self.config.ObjectType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
aacparser.FillADTSHeader(self.adtshdr, self.config, 1024, len(pkt.Data))
|
||||
if _, err = self.w.Write(self.adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.w.Write(pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type Demuxer struct {
|
||||
r *bufio.Reader
|
||||
config aacparser.MPEG4AudioConfig
|
||||
codecdata av.CodecData
|
||||
ts time.Duration
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
r: bufio.NewReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if self.codecdata == nil {
|
||||
var adtshdr []byte
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
if adtshdr, err = self.r.Peek(9); err != nil {
|
||||
return
|
||||
}
|
||||
if config, _, _, _, err = aacparser.ParseADTSHeader(adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
if self.codecdata, err = aacparser.NewCodecDataFromMPEG4AudioConfig(config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
streams = []av.CodecData{self.codecdata}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
var adtshdr []byte
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
var hdrlen, framelen, samples int
|
||||
if adtshdr, err = self.r.Peek(9); err != nil {
|
||||
return
|
||||
}
|
||||
if config, hdrlen, framelen, samples, err = aacparser.ParseADTSHeader(adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkt.Data = make([]byte, framelen)
|
||||
if _, err = io.ReadFull(self.r, pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
pkt.Data = pkt.Data[hdrlen:]
|
||||
|
||||
pkt.Time = self.ts
|
||||
self.ts += time.Duration(samples) * time.Second / time.Duration(config.SampleRate)
|
||||
return
|
||||
}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".aac"
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r)
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w)
|
||||
}
|
||||
|
||||
h.Probe = func(b []byte) bool {
|
||||
_, _, _, _, err := aacparser.ParseADTSHeader(b)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
h.CodecTypes = []av.CodecType{av.AAC}
|
||||
}
|
||||
495
format/flv/flv.go
Normal file
495
format/flv/flv.go
Normal file
@@ -0,0 +1,495 @@
|
||||
package flv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/av/avutil"
|
||||
"github.com/deepch/vdk/codec"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/fake"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/flv/flvio"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
var MaxProbePacketCount = 20
|
||||
|
||||
func NewMetadataByStreams(streams []av.CodecData) (metadata flvio.AMFMap, err error) {
|
||||
metadata = flvio.AMFMap{}
|
||||
|
||||
for _, _stream := range streams {
|
||||
typ := _stream.Type()
|
||||
switch {
|
||||
case typ.IsVideo():
|
||||
stream := _stream.(av.VideoCodecData)
|
||||
switch typ {
|
||||
case av.H264:
|
||||
metadata["videocodecid"] = flvio.VIDEO_H264
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: metadata: unsupported video codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
|
||||
metadata["width"] = stream.Width()
|
||||
metadata["height"] = stream.Height()
|
||||
metadata["displayWidth"] = stream.Width()
|
||||
metadata["displayHeight"] = stream.Height()
|
||||
|
||||
case typ.IsAudio():
|
||||
stream := _stream.(av.AudioCodecData)
|
||||
switch typ {
|
||||
case av.AAC:
|
||||
metadata["audiocodecid"] = flvio.SOUND_AAC
|
||||
|
||||
case av.SPEEX:
|
||||
metadata["audiocodecid"] = flvio.SOUND_SPEEX
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: metadata: unsupported audio codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
|
||||
metadata["audiosamplerate"] = stream.SampleRate()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Prober struct {
|
||||
HasAudio, HasVideo bool
|
||||
GotAudio, GotVideo bool
|
||||
VideoStreamIdx, AudioStreamIdx int
|
||||
PushedCount int
|
||||
Streams []av.CodecData
|
||||
CachedPkts []av.Packet
|
||||
}
|
||||
|
||||
func (self *Prober) CacheTag(_tag flvio.Tag, timestamp int32) {
|
||||
pkt, _ := self.TagToPacket(_tag, timestamp)
|
||||
self.CachedPkts = append(self.CachedPkts, pkt)
|
||||
}
|
||||
|
||||
func (self *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
||||
self.PushedCount++
|
||||
|
||||
if self.PushedCount > MaxProbePacketCount {
|
||||
err = fmt.Errorf("flv: max probe packet count reached")
|
||||
return
|
||||
}
|
||||
|
||||
switch tag.Type {
|
||||
case flvio.TAG_VIDEO:
|
||||
switch tag.AVCPacketType {
|
||||
case flvio.AVC_SEQHDR:
|
||||
if !self.GotVideo {
|
||||
var stream h264parser.CodecData
|
||||
if stream, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil {
|
||||
err = fmt.Errorf("flv: h264 seqhdr invalid")
|
||||
return
|
||||
}
|
||||
self.VideoStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotVideo = true
|
||||
}
|
||||
|
||||
case flvio.AVC_NALU:
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.TAG_AUDIO:
|
||||
switch tag.SoundFormat {
|
||||
case flvio.SOUND_AAC:
|
||||
switch tag.AACPacketType {
|
||||
case flvio.AAC_SEQHDR:
|
||||
if !self.GotAudio {
|
||||
var stream aacparser.CodecData
|
||||
if stream, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil {
|
||||
err = fmt.Errorf("flv: aac seqhdr invalid")
|
||||
return
|
||||
}
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
}
|
||||
|
||||
case flvio.AAC_RAW:
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.SOUND_SPEEX:
|
||||
if !self.GotAudio {
|
||||
stream := codec.NewSpeexCodecData(16000, tag.ChannelLayout())
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.SOUND_NELLYMOSER:
|
||||
if !self.GotAudio {
|
||||
stream := fake.CodecData{
|
||||
CodecType_: av.NELLYMOSER,
|
||||
SampleRate_: 16000,
|
||||
SampleFormat_: av.S16,
|
||||
ChannelLayout_: tag.ChannelLayout(),
|
||||
}
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) Probed() (ok bool) {
|
||||
if self.HasAudio || self.HasVideo {
|
||||
if self.HasAudio == self.GotAudio && self.HasVideo == self.GotVideo {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if self.PushedCount == MaxProbePacketCount {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) TagToPacket(tag flvio.Tag, timestamp int32) (pkt av.Packet, ok bool) {
|
||||
switch tag.Type {
|
||||
case flvio.TAG_VIDEO:
|
||||
pkt.Idx = int8(self.VideoStreamIdx)
|
||||
switch tag.AVCPacketType {
|
||||
case flvio.AVC_NALU:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
pkt.CompositionTime = flvio.TsToTime(tag.CompositionTime)
|
||||
pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY
|
||||
}
|
||||
|
||||
case flvio.TAG_AUDIO:
|
||||
pkt.Idx = int8(self.AudioStreamIdx)
|
||||
switch tag.SoundFormat {
|
||||
case flvio.SOUND_AAC:
|
||||
switch tag.AACPacketType {
|
||||
case flvio.AAC_RAW:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
}
|
||||
|
||||
case flvio.SOUND_SPEEX:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
|
||||
case flvio.SOUND_NELLYMOSER:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
}
|
||||
}
|
||||
|
||||
pkt.Time = flvio.TsToTime(timestamp)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) Empty() bool {
|
||||
return len(self.CachedPkts) == 0
|
||||
}
|
||||
|
||||
func (self *Prober) PopPacket() av.Packet {
|
||||
pkt := self.CachedPkts[0]
|
||||
self.CachedPkts = self.CachedPkts[1:]
|
||||
return pkt
|
||||
}
|
||||
|
||||
func CodecDataToTag(stream av.CodecData) (_tag flvio.Tag, ok bool, err error) {
|
||||
switch stream.Type() {
|
||||
case av.H264:
|
||||
h264 := stream.(h264parser.CodecData)
|
||||
tag := flvio.Tag{
|
||||
Type: flvio.TAG_VIDEO,
|
||||
AVCPacketType: flvio.AVC_SEQHDR,
|
||||
CodecID: flvio.VIDEO_H264,
|
||||
Data: h264.AVCDecoderConfRecordBytes(),
|
||||
FrameType: flvio.FRAME_KEY,
|
||||
}
|
||||
ok = true
|
||||
_tag = tag
|
||||
|
||||
case av.NELLYMOSER:
|
||||
case av.SPEEX:
|
||||
|
||||
case av.AAC:
|
||||
aac := stream.(aacparser.CodecData)
|
||||
tag := flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_AAC,
|
||||
SoundRate: flvio.SOUND_44Khz,
|
||||
AACPacketType: flvio.AAC_SEQHDR,
|
||||
Data: aac.MPEG4AudioConfigBytes(),
|
||||
}
|
||||
switch aac.SampleFormat().BytesPerSample() {
|
||||
case 1:
|
||||
tag.SoundSize = flvio.SOUND_8BIT
|
||||
default:
|
||||
tag.SoundSize = flvio.SOUND_16BIT
|
||||
}
|
||||
switch aac.ChannelLayout().Count() {
|
||||
case 1:
|
||||
tag.SoundType = flvio.SOUND_MONO
|
||||
case 2:
|
||||
tag.SoundType = flvio.SOUND_STEREO
|
||||
}
|
||||
ok = true
|
||||
_tag = tag
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: unspported codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PacketToTag(pkt av.Packet, stream av.CodecData) (tag flvio.Tag, timestamp int32) {
|
||||
switch stream.Type() {
|
||||
case av.H264:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_VIDEO,
|
||||
AVCPacketType: flvio.AVC_NALU,
|
||||
CodecID: flvio.VIDEO_H264,
|
||||
Data: pkt.Data,
|
||||
CompositionTime: flvio.TimeToTs(pkt.CompositionTime),
|
||||
}
|
||||
if pkt.IsKeyFrame {
|
||||
tag.FrameType = flvio.FRAME_KEY
|
||||
} else {
|
||||
tag.FrameType = flvio.FRAME_INTER
|
||||
}
|
||||
|
||||
case av.AAC:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_AAC,
|
||||
SoundRate: flvio.SOUND_44Khz,
|
||||
AACPacketType: flvio.AAC_RAW,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
astream := stream.(av.AudioCodecData)
|
||||
switch astream.SampleFormat().BytesPerSample() {
|
||||
case 1:
|
||||
tag.SoundSize = flvio.SOUND_8BIT
|
||||
default:
|
||||
tag.SoundSize = flvio.SOUND_16BIT
|
||||
}
|
||||
switch astream.ChannelLayout().Count() {
|
||||
case 1:
|
||||
tag.SoundType = flvio.SOUND_MONO
|
||||
case 2:
|
||||
tag.SoundType = flvio.SOUND_STEREO
|
||||
}
|
||||
|
||||
case av.SPEEX:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_SPEEX,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
|
||||
case av.NELLYMOSER:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_NELLYMOSER,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
}
|
||||
|
||||
timestamp = flvio.TimeToTs(pkt.Time)
|
||||
return
|
||||
}
|
||||
|
||||
type Muxer struct {
|
||||
bufw writeFlusher
|
||||
b []byte
|
||||
streams []av.CodecData
|
||||
}
|
||||
|
||||
type writeFlusher interface {
|
||||
io.Writer
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func NewMuxerWriteFlusher(w writeFlusher) *Muxer {
|
||||
return &Muxer{
|
||||
bufw: w,
|
||||
b: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return NewMuxerWriteFlusher(bufio.NewWriterSize(w, pio.RecommendBufioSize))
|
||||
}
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC, av.SPEEX}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
var flags uint8
|
||||
for _, stream := range streams {
|
||||
if stream.Type().IsVideo() {
|
||||
flags |= flvio.FILE_HAS_VIDEO
|
||||
} else if stream.Type().IsAudio() {
|
||||
flags |= flvio.FILE_HAS_AUDIO
|
||||
}
|
||||
}
|
||||
|
||||
n := flvio.FillFileHeader(self.b, flags)
|
||||
if _, err = self.bufw.Write(self.b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, stream := range streams {
|
||||
var tag flvio.Tag
|
||||
var ok bool
|
||||
if tag, ok, err = CodecDataToTag(stream); err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
if err = flvio.WriteTag(self.bufw, tag, 0, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.streams = streams
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
tag, timestamp := PacketToTag(pkt, stream)
|
||||
|
||||
if err = flvio.WriteTag(self.bufw, tag, timestamp, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
if err = self.bufw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Demuxer struct {
|
||||
prober *Prober
|
||||
bufr *bufio.Reader
|
||||
b []byte
|
||||
stage int
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
bufr: bufio.NewReaderSize(r, pio.RecommendBufioSize),
|
||||
prober: &Prober{},
|
||||
b: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) prepare() (err error) {
|
||||
for self.stage < 2 {
|
||||
switch self.stage {
|
||||
case 0:
|
||||
if _, err = io.ReadFull(self.bufr, self.b[:flvio.FileHeaderLength]); err != nil {
|
||||
return
|
||||
}
|
||||
var flags uint8
|
||||
var skip int
|
||||
if flags, skip, err = flvio.ParseFileHeader(self.b); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.bufr.Discard(skip); err != nil {
|
||||
return
|
||||
}
|
||||
if flags&flvio.FILE_HAS_AUDIO != 0 {
|
||||
self.prober.HasAudio = true
|
||||
}
|
||||
if flags&flvio.FILE_HAS_VIDEO != 0 {
|
||||
self.prober.HasVideo = true
|
||||
}
|
||||
self.stage++
|
||||
|
||||
case 1:
|
||||
for !self.prober.Probed() {
|
||||
var tag flvio.Tag
|
||||
var timestamp int32
|
||||
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.prober.PushTag(tag, timestamp); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.stage++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
streams = self.prober.Streams
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !self.prober.Empty() {
|
||||
pkt = self.prober.PopPacket()
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
var tag flvio.Tag
|
||||
var timestamp int32
|
||||
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if pkt, ok = self.prober.TagToPacket(tag, timestamp); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Probe = func(b []byte) bool {
|
||||
return b[0] == 'F' && b[1] == 'L' && b[2] == 'V'
|
||||
}
|
||||
|
||||
h.Ext = ".flv"
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r)
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w)
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
||||
467
format/flv/flvio/amf0.go
Normal file
467
format/flv/flvio/amf0.go
Normal file
@@ -0,0 +1,467 @@
|
||||
package flvio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type AMF0ParseError struct {
|
||||
Offset int
|
||||
Message string
|
||||
Next *AMF0ParseError
|
||||
}
|
||||
|
||||
func (self *AMF0ParseError) Error() string {
|
||||
s := []string{}
|
||||
for p := self; p != nil; p = p.Next {
|
||||
s = append(s, fmt.Sprintf("%s:%d", p.Message, p.Offset))
|
||||
}
|
||||
return "amf0 parse error: " + strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func amf0ParseErr(message string, offset int, err error) error {
|
||||
next, _ := err.(*AMF0ParseError)
|
||||
return &AMF0ParseError{
|
||||
Offset: offset,
|
||||
Message: message,
|
||||
Next: next,
|
||||
}
|
||||
}
|
||||
|
||||
type AMFMap map[string]interface{}
|
||||
type AMFArray []interface{}
|
||||
type AMFECMAArray map[string]interface{}
|
||||
|
||||
func parseBEFloat64(b []byte) float64 {
|
||||
return math.Float64frombits(pio.U64BE(b))
|
||||
}
|
||||
|
||||
func fillBEFloat64(b []byte, f float64) int {
|
||||
pio.PutU64BE(b, math.Float64bits(f))
|
||||
return 8
|
||||
}
|
||||
|
||||
const lenAMF0Number = 9
|
||||
|
||||
func fillAMF0Number(b []byte, f float64) int {
|
||||
b[0] = numbermarker
|
||||
fillBEFloat64(b[1:], f)
|
||||
return lenAMF0Number
|
||||
}
|
||||
|
||||
const (
|
||||
amf3undefinedmarker = iota
|
||||
amf3nullmarker
|
||||
amf3falsemarker
|
||||
amf3truemarker
|
||||
amf3integermarker
|
||||
amf3doublemarker
|
||||
amf3stringmarker
|
||||
amf3xmldocmarker
|
||||
amf3datemarker
|
||||
amf3arraymarker
|
||||
amf3objectmarker
|
||||
amf3xmlmarker
|
||||
amf3bytearraymarker
|
||||
amf3vectorintmarker
|
||||
amf3vectoruintmarker
|
||||
amf3vectordoublemarker
|
||||
amf3vectorobjectmarker
|
||||
amf3dictionarymarker
|
||||
)
|
||||
|
||||
const (
|
||||
numbermarker = iota
|
||||
booleanmarker
|
||||
stringmarker
|
||||
objectmarker
|
||||
movieclipmarker
|
||||
nullmarker
|
||||
undefinedmarker
|
||||
referencemarker
|
||||
ecmaarraymarker
|
||||
objectendmarker
|
||||
strictarraymarker
|
||||
datemarker
|
||||
longstringmarker
|
||||
unsupportedmarker
|
||||
recordsetmarker
|
||||
xmldocumentmarker
|
||||
typedobjectmarker
|
||||
avmplusobjectmarker
|
||||
)
|
||||
|
||||
func LenAMF0Val(_val interface{}) (n int) {
|
||||
switch val := _val.(type) {
|
||||
case int8:
|
||||
n += lenAMF0Number
|
||||
case int16:
|
||||
n += lenAMF0Number
|
||||
case int32:
|
||||
n += lenAMF0Number
|
||||
case int64:
|
||||
n += lenAMF0Number
|
||||
case int:
|
||||
n += lenAMF0Number
|
||||
case uint8:
|
||||
n += lenAMF0Number
|
||||
case uint16:
|
||||
n += lenAMF0Number
|
||||
case uint32:
|
||||
n += lenAMF0Number
|
||||
case uint64:
|
||||
n += lenAMF0Number
|
||||
case uint:
|
||||
n += lenAMF0Number
|
||||
case float32:
|
||||
n += lenAMF0Number
|
||||
case float64:
|
||||
n += lenAMF0Number
|
||||
|
||||
case string:
|
||||
u := len(val)
|
||||
if u <= 65536 {
|
||||
n += 3
|
||||
} else {
|
||||
n += 5
|
||||
}
|
||||
n += int(u)
|
||||
|
||||
case AMFECMAArray:
|
||||
n += 5
|
||||
for k, v := range val {
|
||||
n += 2 + len(k)
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
n += 3
|
||||
|
||||
case AMFMap:
|
||||
n++
|
||||
for k, v := range val {
|
||||
if len(k) > 0 {
|
||||
n += 2 + len(k)
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
}
|
||||
n += 3
|
||||
|
||||
case AMFArray:
|
||||
n += 5
|
||||
for _, v := range val {
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
|
||||
case time.Time:
|
||||
n += 1 + 8 + 2
|
||||
|
||||
case bool:
|
||||
n += 2
|
||||
|
||||
case nil:
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func FillAMF0Val(b []byte, _val interface{}) (n int) {
|
||||
switch val := _val.(type) {
|
||||
case int8:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int16:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint8:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint16:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case float32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case float64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
|
||||
case string:
|
||||
u := len(val)
|
||||
if u <= 65536 {
|
||||
b[n] = stringmarker
|
||||
n++
|
||||
pio.PutU16BE(b[n:], uint16(u))
|
||||
n += 2
|
||||
} else {
|
||||
b[n] = longstringmarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(u))
|
||||
n += 4
|
||||
}
|
||||
copy(b[n:], []byte(val))
|
||||
n += len(val)
|
||||
|
||||
case AMFECMAArray:
|
||||
b[n] = ecmaarraymarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(len(val)))
|
||||
n += 4
|
||||
for k, v := range val {
|
||||
pio.PutU16BE(b[n:], uint16(len(k)))
|
||||
n += 2
|
||||
copy(b[n:], []byte(k))
|
||||
n += len(k)
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
pio.PutU24BE(b[n:], 0x000009)
|
||||
n += 3
|
||||
|
||||
case AMFMap:
|
||||
b[n] = objectmarker
|
||||
n++
|
||||
for k, v := range val {
|
||||
if len(k) > 0 {
|
||||
pio.PutU16BE(b[n:], uint16(len(k)))
|
||||
n += 2
|
||||
copy(b[n:], []byte(k))
|
||||
n += len(k)
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
}
|
||||
pio.PutU24BE(b[n:], 0x000009)
|
||||
n += 3
|
||||
|
||||
case AMFArray:
|
||||
b[n] = strictarraymarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(len(val)))
|
||||
n += 4
|
||||
for _, v := range val {
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
|
||||
case time.Time:
|
||||
b[n] = datemarker
|
||||
n++
|
||||
u := val.UnixNano()
|
||||
f := float64(u / 1000000)
|
||||
n += fillBEFloat64(b[n:], f)
|
||||
pio.PutU16BE(b[n:], uint16(0))
|
||||
n += 2
|
||||
|
||||
case bool:
|
||||
b[n] = booleanmarker
|
||||
n++
|
||||
var u uint8
|
||||
if val {
|
||||
u = 1
|
||||
} else {
|
||||
u = 0
|
||||
}
|
||||
b[n] = u
|
||||
n++
|
||||
|
||||
case nil:
|
||||
b[n] = nullmarker
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseAMF0Val(b []byte) (val interface{}, n int, err error) {
|
||||
return parseAMF0Val(b, 0)
|
||||
}
|
||||
|
||||
func parseAMF0Val(b []byte, offset int) (val interface{}, n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("marker", offset+n, err)
|
||||
return
|
||||
}
|
||||
marker := b[n]
|
||||
n++
|
||||
|
||||
switch marker {
|
||||
case numbermarker:
|
||||
if len(b) < n+8 {
|
||||
err = amf0ParseErr("number", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = parseBEFloat64(b[n:])
|
||||
n += 8
|
||||
|
||||
case booleanmarker:
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("boolean", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = b[n] != 0
|
||||
n++
|
||||
|
||||
case stringmarker:
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("string.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("string.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = string(b[n : n+length])
|
||||
n += length
|
||||
|
||||
case objectmarker:
|
||||
obj := AMFMap{}
|
||||
for {
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("object.key.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("object.key.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
okey := string(b[n : n+length])
|
||||
n += length
|
||||
|
||||
var nval int
|
||||
var oval interface{}
|
||||
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("object.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
|
||||
obj[okey] = oval
|
||||
}
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("object.end", offset+n, err)
|
||||
return
|
||||
}
|
||||
n++
|
||||
val = obj
|
||||
|
||||
case nullmarker:
|
||||
case undefinedmarker:
|
||||
|
||||
case ecmaarraymarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("array.count", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 4
|
||||
|
||||
obj := AMFMap{}
|
||||
for {
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("array.key.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("array.key.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
okey := string(b[n : n+length])
|
||||
n += length
|
||||
|
||||
var nval int
|
||||
var oval interface{}
|
||||
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("array.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
|
||||
obj[okey] = oval
|
||||
}
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("array.end", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 1
|
||||
val = obj
|
||||
|
||||
case objectendmarker:
|
||||
if len(b) < n+3 {
|
||||
err = amf0ParseErr("objectend", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 3
|
||||
|
||||
case strictarraymarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("strictarray.count", offset+n, err)
|
||||
return
|
||||
}
|
||||
count := int(pio.U32BE(b[n:]))
|
||||
n += 4
|
||||
|
||||
obj := make(AMFArray, count)
|
||||
for i := 0; i < int(count); i++ {
|
||||
var nval int
|
||||
if obj[i], nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("strictarray.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
}
|
||||
val = obj
|
||||
|
||||
case datemarker:
|
||||
if len(b) < n+8+2 {
|
||||
err = amf0ParseErr("date", offset+n, err)
|
||||
return
|
||||
}
|
||||
ts := parseBEFloat64(b[n:])
|
||||
n += 8 + 2
|
||||
|
||||
val = time.Unix(int64(ts/1000), (int64(ts)%1000)*1000000)
|
||||
|
||||
case longstringmarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("longstring.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U32BE(b[n:]))
|
||||
n += 4
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("longstring.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = string(b[n : n+length])
|
||||
n += length
|
||||
|
||||
default:
|
||||
err = amf0ParseErr(fmt.Sprintf("invalidmarker=%d", marker), offset+n, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
411
format/flv/flvio/flvio.go
Normal file
411
format/flv/flvio/flvio.go
Normal file
@@ -0,0 +1,411 @@
|
||||
package flvio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
func TsToTime(ts int32) time.Duration {
|
||||
return time.Millisecond * time.Duration(ts)
|
||||
}
|
||||
|
||||
func TimeToTs(tm time.Duration) int32 {
|
||||
return int32(tm / time.Millisecond)
|
||||
}
|
||||
|
||||
const MaxTagSubHeaderLength = 16
|
||||
|
||||
const (
|
||||
TAG_AUDIO = 8
|
||||
TAG_VIDEO = 9
|
||||
TAG_SCRIPTDATA = 18
|
||||
)
|
||||
|
||||
const (
|
||||
SOUND_MP3 = 2
|
||||
SOUND_NELLYMOSER_16KHZ_MONO = 4
|
||||
SOUND_NELLYMOSER_8KHZ_MONO = 5
|
||||
SOUND_NELLYMOSER = 6
|
||||
SOUND_ALAW = 7
|
||||
SOUND_MULAW = 8
|
||||
SOUND_AAC = 10
|
||||
SOUND_SPEEX = 11
|
||||
|
||||
SOUND_5_5Khz = 0
|
||||
SOUND_11Khz = 1
|
||||
SOUND_22Khz = 2
|
||||
SOUND_44Khz = 3
|
||||
|
||||
SOUND_8BIT = 0
|
||||
SOUND_16BIT = 1
|
||||
|
||||
SOUND_MONO = 0
|
||||
SOUND_STEREO = 1
|
||||
|
||||
AAC_SEQHDR = 0
|
||||
AAC_RAW = 1
|
||||
)
|
||||
|
||||
const (
|
||||
AVC_SEQHDR = 0
|
||||
AVC_NALU = 1
|
||||
AVC_EOS = 2
|
||||
|
||||
FRAME_KEY = 1
|
||||
FRAME_INTER = 2
|
||||
|
||||
VIDEO_H264 = 7
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Type uint8
|
||||
|
||||
/*
|
||||
SoundFormat: UB[4]
|
||||
0 = Linear PCM, platform endian
|
||||
1 = ADPCM
|
||||
2 = MP3
|
||||
3 = Linear PCM, little endian
|
||||
4 = Nellymoser 16-kHz mono
|
||||
5 = Nellymoser 8-kHz mono
|
||||
6 = Nellymoser
|
||||
7 = G.711 A-law logarithmic PCM
|
||||
8 = G.711 mu-law logarithmic PCM
|
||||
9 = reserved
|
||||
10 = AAC
|
||||
11 = Speex
|
||||
14 = MP3 8-Khz
|
||||
15 = Device-specific sound
|
||||
Formats 7, 8, 14, and 15 are reserved for internal use
|
||||
AAC is supported in Flash Player 9,0,115,0 and higher.
|
||||
Speex is supported in Flash Player 10 and higher.
|
||||
*/
|
||||
SoundFormat uint8
|
||||
|
||||
/*
|
||||
SoundRate: UB[2]
|
||||
Sampling rate
|
||||
0 = 5.5-kHz For AAC: always 3
|
||||
1 = 11-kHz
|
||||
2 = 22-kHz
|
||||
3 = 44-kHz
|
||||
*/
|
||||
SoundRate uint8
|
||||
|
||||
/*
|
||||
SoundSize: UB[1]
|
||||
0 = snd8Bit
|
||||
1 = snd16Bit
|
||||
Size of each sample.
|
||||
This parameter only pertains to uncompressed formats.
|
||||
Compressed formats always decode to 16 bits internally
|
||||
*/
|
||||
SoundSize uint8
|
||||
|
||||
/*
|
||||
SoundType: UB[1]
|
||||
0 = sndMono
|
||||
1 = sndStereo
|
||||
Mono or stereo sound For Nellymoser: always 0
|
||||
For AAC: always 1
|
||||
*/
|
||||
SoundType uint8
|
||||
|
||||
/*
|
||||
0: AAC sequence header
|
||||
1: AAC raw
|
||||
*/
|
||||
AACPacketType uint8
|
||||
|
||||
/*
|
||||
1: keyframe (for AVC, a seekable frame)
|
||||
2: inter frame (for AVC, a non- seekable frame)
|
||||
3: disposable inter frame (H.263 only)
|
||||
4: generated keyframe (reserved for server use only)
|
||||
5: video info/command frame
|
||||
*/
|
||||
FrameType uint8
|
||||
|
||||
/*
|
||||
1: JPEG (currently unused)
|
||||
2: Sorenson H.263
|
||||
3: Screen video
|
||||
4: On2 VP6
|
||||
5: On2 VP6 with alpha channel
|
||||
6: Screen video version 2
|
||||
7: AVC
|
||||
*/
|
||||
CodecID uint8
|
||||
|
||||
/*
|
||||
0: AVC sequence header
|
||||
1: AVC NALU
|
||||
2: AVC end of sequence (lower level NALU sequence ender is not required or supported)
|
||||
*/
|
||||
AVCPacketType uint8
|
||||
|
||||
CompositionTime int32
|
||||
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (self Tag) ChannelLayout() av.ChannelLayout {
|
||||
if self.SoundType == SOUND_MONO {
|
||||
return av.CH_MONO
|
||||
} else {
|
||||
return av.CH_STEREO
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Tag) audioParseHeader(b []byte) (n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("audiodata: parse invalid")
|
||||
return
|
||||
}
|
||||
|
||||
flags := b[n]
|
||||
n++
|
||||
self.SoundFormat = flags >> 4
|
||||
self.SoundRate = (flags >> 2) & 0x3
|
||||
self.SoundSize = (flags >> 1) & 0x1
|
||||
self.SoundType = flags & 0x1
|
||||
|
||||
switch self.SoundFormat {
|
||||
case SOUND_AAC:
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("audiodata: parse invalid")
|
||||
return
|
||||
}
|
||||
self.AACPacketType = b[n]
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) audioFillHeader(b []byte) (n int) {
|
||||
var flags uint8
|
||||
flags |= self.SoundFormat << 4
|
||||
flags |= self.SoundRate << 2
|
||||
flags |= self.SoundSize << 1
|
||||
flags |= self.SoundType
|
||||
b[n] = flags
|
||||
n++
|
||||
|
||||
switch self.SoundFormat {
|
||||
case SOUND_AAC:
|
||||
b[n] = self.AACPacketType
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Tag) videoParseHeader(b []byte) (n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("videodata: parse invalid")
|
||||
return
|
||||
}
|
||||
flags := b[n]
|
||||
self.FrameType = flags >> 4
|
||||
self.CodecID = flags & 0xf
|
||||
n++
|
||||
|
||||
if self.FrameType == FRAME_INTER || self.FrameType == FRAME_KEY {
|
||||
if len(b) < n+4 {
|
||||
err = fmt.Errorf("videodata: parse invalid")
|
||||
return
|
||||
}
|
||||
self.AVCPacketType = b[n]
|
||||
n++
|
||||
|
||||
self.CompositionTime = pio.I24BE(b[n:])
|
||||
n += 3
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) videoFillHeader(b []byte) (n int) {
|
||||
flags := self.FrameType<<4 | self.CodecID
|
||||
b[n] = flags
|
||||
n++
|
||||
b[n] = self.AVCPacketType
|
||||
n++
|
||||
pio.PutI24BE(b[n:], self.CompositionTime)
|
||||
n += 3
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) FillHeader(b []byte) (n int) {
|
||||
switch self.Type {
|
||||
case TAG_AUDIO:
|
||||
return self.audioFillHeader(b)
|
||||
|
||||
case TAG_VIDEO:
|
||||
return self.videoFillHeader(b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Tag) ParseHeader(b []byte) (n int, err error) {
|
||||
switch self.Type {
|
||||
case TAG_AUDIO:
|
||||
return self.audioParseHeader(b)
|
||||
|
||||
case TAG_VIDEO:
|
||||
return self.videoParseHeader(b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
// TypeFlagsReserved UB[5]
|
||||
// TypeFlagsAudio UB[1] Audio tags are present
|
||||
// TypeFlagsReserved UB[1] Must be 0
|
||||
// TypeFlagsVideo UB[1] Video tags are present
|
||||
FILE_HAS_AUDIO = 0x4
|
||||
FILE_HAS_VIDEO = 0x1
|
||||
)
|
||||
|
||||
const TagHeaderLength = 11
|
||||
const TagTrailerLength = 4
|
||||
|
||||
func ParseTagHeader(b []byte) (tag Tag, ts int32, datalen int, err error) {
|
||||
tagtype := b[0]
|
||||
|
||||
switch tagtype {
|
||||
case TAG_AUDIO, TAG_VIDEO, TAG_SCRIPTDATA:
|
||||
tag = Tag{Type: tagtype}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flvio: ReadTag tagtype=%d invalid", tagtype)
|
||||
return
|
||||
}
|
||||
|
||||
datalen = int(pio.U24BE(b[1:4]))
|
||||
|
||||
var tslo uint32
|
||||
var tshi uint8
|
||||
tslo = pio.U24BE(b[4:7])
|
||||
tshi = b[7]
|
||||
ts = int32(tslo | uint32(tshi)<<24)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadTag(r io.Reader, b []byte) (tag Tag, ts int32, err error) {
|
||||
if _, err = io.ReadFull(r, b[:TagHeaderLength]); err != nil {
|
||||
return
|
||||
}
|
||||
var datalen int
|
||||
if tag, ts, datalen, err = ParseTagHeader(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, datalen)
|
||||
if _, err = io.ReadFull(r, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var n int
|
||||
if n, err = (&tag).ParseHeader(data); err != nil {
|
||||
return
|
||||
}
|
||||
tag.Data = data[n:]
|
||||
|
||||
if _, err = io.ReadFull(r, b[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func FillTagHeader(b []byte, tagtype uint8, datalen int, ts int32) (n int) {
|
||||
b[n] = tagtype
|
||||
n++
|
||||
pio.PutU24BE(b[n:], uint32(datalen))
|
||||
n += 3
|
||||
pio.PutU24BE(b[n:], uint32(ts&0xffffff))
|
||||
n += 3
|
||||
b[n] = uint8(ts >> 24)
|
||||
n++
|
||||
pio.PutI24BE(b[n:], 0)
|
||||
n += 3
|
||||
return
|
||||
}
|
||||
|
||||
func FillTagTrailer(b []byte, datalen int) (n int) {
|
||||
pio.PutU32BE(b[n:], uint32(datalen+TagHeaderLength))
|
||||
n += 4
|
||||
return
|
||||
}
|
||||
|
||||
func WriteTag(w io.Writer, tag Tag, ts int32, b []byte) (err error) {
|
||||
data := tag.Data
|
||||
|
||||
n := tag.FillHeader(b[TagHeaderLength:])
|
||||
datalen := len(data) + n
|
||||
|
||||
n += FillTagHeader(b, tag.Type, datalen, ts)
|
||||
|
||||
if _, err = w.Write(b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = FillTagTrailer(b, datalen)
|
||||
if _, err = w.Write(b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const FileHeaderLength = 9
|
||||
|
||||
func FillFileHeader(b []byte, flags uint8) (n int) {
|
||||
// 'FLV', version 1
|
||||
pio.PutU32BE(b[n:], 0x464c5601)
|
||||
n += 4
|
||||
|
||||
b[n] = flags
|
||||
n++
|
||||
|
||||
// DataOffset: UI32 Offset in bytes from start of file to start of body (that is, size of header)
|
||||
// The DataOffset field usually has a value of 9 for FLV version 1.
|
||||
pio.PutU32BE(b[n:], 9)
|
||||
n += 4
|
||||
|
||||
// PreviousTagSize0: UI32 Always 0
|
||||
pio.PutU32BE(b[n:], 0)
|
||||
n += 4
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseFileHeader(b []byte) (flags uint8, skip int, err error) {
|
||||
flv := pio.U24BE(b[0:3])
|
||||
if flv != 0x464c56 { // 'FLV'
|
||||
err = fmt.Errorf("flvio: file header cc3 invalid")
|
||||
return
|
||||
}
|
||||
|
||||
flags = b[4]
|
||||
|
||||
skip = int(pio.U32BE(b[5:9])) - 9 + 4
|
||||
if skip < 0 {
|
||||
err = fmt.Errorf("flvio: file header datasize invalid")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
20
format/format.go
Normal file
20
format/format.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"github.com/deepch/vdk/av/avutil"
|
||||
"github.com/deepch/vdk/format/aac"
|
||||
"github.com/deepch/vdk/format/flv"
|
||||
"github.com/deepch/vdk/format/mp4"
|
||||
"github.com/deepch/vdk/format/rtmp"
|
||||
"github.com/deepch/vdk/format/rtsp"
|
||||
"github.com/deepch/vdk/format/ts"
|
||||
)
|
||||
|
||||
func RegisterAll() {
|
||||
avutil.DefaultHandlers.Add(mp4.Handler)
|
||||
avutil.DefaultHandlers.Add(ts.Handler)
|
||||
avutil.DefaultHandlers.Add(rtmp.Handler)
|
||||
avutil.DefaultHandlers.Add(rtsp.Handler)
|
||||
avutil.DefaultHandlers.Add(flv.Handler)
|
||||
avutil.DefaultHandlers.Add(aac.Handler)
|
||||
}
|
||||
446
format/mp4/demuxer.go
Normal file
446
format/mp4/demuxer.go
Normal file
@@ -0,0 +1,446 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
)
|
||||
|
||||
type Demuxer struct {
|
||||
r io.ReadSeeker
|
||||
streams []*Stream
|
||||
movieAtom *mp4io.Movie
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.ReadSeeker) *Demuxer {
|
||||
return &Demuxer{
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
for _, stream := range self.streams {
|
||||
streams = append(streams, stream.CodecData)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) readat(pos int64, b []byte) (err error) {
|
||||
if _, err = self.r.Seek(pos, 0); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.ReadFull(self.r, b); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) probe() (err error) {
|
||||
if self.movieAtom != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var moov *mp4io.Movie
|
||||
var atoms []mp4io.Atom
|
||||
|
||||
if atoms, err = mp4io.ReadFileAtoms(self.r); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.r.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, atom := range atoms {
|
||||
if atom.Tag() == mp4io.MOOV {
|
||||
moov = atom.(*mp4io.Movie)
|
||||
}
|
||||
}
|
||||
|
||||
if moov == nil {
|
||||
err = fmt.Errorf("mp4: 'moov' atom not found")
|
||||
return
|
||||
}
|
||||
|
||||
self.streams = []*Stream{}
|
||||
for i, atrack := range moov.Tracks {
|
||||
stream := &Stream{
|
||||
trackAtom: atrack,
|
||||
demuxer: self,
|
||||
idx: i,
|
||||
}
|
||||
if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil {
|
||||
stream.sample = atrack.Media.Info.Sample
|
||||
stream.timeScale = int64(atrack.Media.Header.TimeScale)
|
||||
} else {
|
||||
err = fmt.Errorf("mp4: sample table not found")
|
||||
return
|
||||
}
|
||||
|
||||
if avc1 := atrack.GetAVC1Conf(); avc1 != nil {
|
||||
if stream.CodecData, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(avc1.Data); err != nil {
|
||||
return
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
} else if esds := atrack.GetElemStreamDesc(); esds != nil {
|
||||
if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(esds.DecConfig); err != nil {
|
||||
return
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
}
|
||||
}
|
||||
|
||||
self.movieAtom = moov
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) setSampleIndex(index int) (err error) {
|
||||
found := false
|
||||
start := 0
|
||||
self.chunkGroupIndex = 0
|
||||
|
||||
for self.chunkIndex = range self.sample.ChunkOffset.Entries {
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(self.chunkIndex+1) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
n := int(self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk)
|
||||
if index >= start && index < start+n {
|
||||
found = true
|
||||
self.sampleIndexInChunk = index - start
|
||||
break
|
||||
}
|
||||
start += n
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in chunk", self.idx)
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
self.sampleOffsetInChunk = int64(self.sampleIndexInChunk) * int64(self.sample.SampleSize.SampleSize)
|
||||
} else {
|
||||
if index >= len(self.sample.SampleSize.Entries) {
|
||||
err = fmt.Errorf("mp4: stream[%d]: sample index out of range", self.idx)
|
||||
return
|
||||
}
|
||||
self.sampleOffsetInChunk = int64(0)
|
||||
for i := index - self.sampleIndexInChunk; i < index; i++ {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[i])
|
||||
}
|
||||
}
|
||||
|
||||
self.dts = int64(0)
|
||||
start = 0
|
||||
found = false
|
||||
self.sttsEntryIndex = 0
|
||||
for self.sttsEntryIndex < len(self.sample.TimeToSample.Entries) {
|
||||
entry := self.sample.TimeToSample.Entries[self.sttsEntryIndex]
|
||||
n := int(entry.Count)
|
||||
if index >= start && index < start+n {
|
||||
self.sampleIndexInSttsEntry = index - start
|
||||
self.dts += int64(index-start) * int64(entry.Duration)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
start += n
|
||||
self.dts += int64(n) * int64(entry.Duration)
|
||||
self.sttsEntryIndex++
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in stts entry", self.idx)
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
start = 0
|
||||
found = false
|
||||
self.cttsEntryIndex = 0
|
||||
for self.cttsEntryIndex < len(self.sample.CompositionOffset.Entries) {
|
||||
n := int(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count)
|
||||
if index >= start && index < start+n {
|
||||
self.sampleIndexInCttsEntry = index - start
|
||||
found = true
|
||||
break
|
||||
}
|
||||
start += n
|
||||
self.cttsEntryIndex++
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in ctts entry", self.idx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
self.syncSampleIndex = 0
|
||||
for self.syncSampleIndex < len(self.sample.SyncSample.Entries)-1 {
|
||||
if self.sample.SyncSample.Entries[self.syncSampleIndex+1]-1 > uint32(index) {
|
||||
break
|
||||
}
|
||||
self.syncSampleIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if false {
|
||||
fmt.Printf("mp4: stream[%d]: setSampleIndex chunkGroupIndex=%d chunkIndex=%d sampleOffsetInChunk=%d\n",
|
||||
self.idx, self.chunkGroupIndex, self.chunkIndex, self.sampleOffsetInChunk)
|
||||
}
|
||||
|
||||
self.sampleIndex = index
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) isSampleValid() bool {
|
||||
if self.chunkIndex >= len(self.sample.ChunkOffset.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.sttsEntryIndex >= len(self.sample.TimeToSample.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
if self.cttsEntryIndex >= len(self.sample.CompositionOffset.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if self.sample.SyncSample != nil {
|
||||
if self.syncSampleIndex >= len(self.sample.SyncSample.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
if self.sampleIndex >= len(self.sample.SampleSize.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Stream) incSampleIndex() (duration int64) {
|
||||
if false {
|
||||
fmt.Printf("incSampleIndex sampleIndex=%d sampleOffsetInChunk=%d sampleIndexInChunk=%d chunkGroupIndex=%d chunkIndex=%d\n",
|
||||
self.sampleIndex, self.sampleOffsetInChunk, self.sampleIndexInChunk, self.chunkGroupIndex, self.chunkIndex)
|
||||
}
|
||||
|
||||
self.sampleIndexInChunk++
|
||||
if uint32(self.sampleIndexInChunk) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk {
|
||||
self.chunkIndex++
|
||||
self.sampleIndexInChunk = 0
|
||||
self.sampleOffsetInChunk = int64(0)
|
||||
} else {
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.SampleSize)
|
||||
} else {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[self.sampleIndex])
|
||||
}
|
||||
}
|
||||
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(self.chunkIndex+1) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
|
||||
sttsEntry := self.sample.TimeToSample.Entries[self.sttsEntryIndex]
|
||||
duration = int64(sttsEntry.Duration)
|
||||
self.sampleIndexInSttsEntry++
|
||||
self.dts += duration
|
||||
if uint32(self.sampleIndexInSttsEntry) == sttsEntry.Count {
|
||||
self.sampleIndexInSttsEntry = 0
|
||||
self.sttsEntryIndex++
|
||||
}
|
||||
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
self.sampleIndexInCttsEntry++
|
||||
if uint32(self.sampleIndexInCttsEntry) == self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count {
|
||||
self.sampleIndexInCttsEntry = 0
|
||||
self.cttsEntryIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
entries := self.sample.SyncSample.Entries
|
||||
if self.syncSampleIndex+1 < len(entries) && entries[self.syncSampleIndex+1]-1 == uint32(self.sampleIndex+1) {
|
||||
self.syncSampleIndex++
|
||||
}
|
||||
}
|
||||
|
||||
self.sampleIndex++
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) sampleCount() int {
|
||||
if self.sample.SampleSize.SampleSize == 0 {
|
||||
chunkGroupIndex := 0
|
||||
count := 0
|
||||
for chunkIndex := range self.sample.ChunkOffset.Entries {
|
||||
n := int(self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk)
|
||||
count += n
|
||||
if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(chunkIndex+1) == self.sample.SampleToChunk.Entries[chunkGroupIndex+1].FirstChunk {
|
||||
chunkGroupIndex++
|
||||
}
|
||||
}
|
||||
return count
|
||||
} else {
|
||||
return len(self.sample.SampleSize.Entries)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
if len(self.streams) == 0 {
|
||||
err = errors.New("mp4: no streams available while trying to read a packet")
|
||||
return
|
||||
}
|
||||
|
||||
var chosen *Stream
|
||||
var chosenidx int
|
||||
for i, stream := range self.streams {
|
||||
if chosen == nil || stream.tsToTime(stream.dts) < chosen.tsToTime(chosen.dts) {
|
||||
chosen = stream
|
||||
chosenidx = i
|
||||
}
|
||||
}
|
||||
if false {
|
||||
fmt.Printf("ReadPacket: chosen index=%v time=%v\n", chosen.idx, chosen.tsToTime(chosen.dts))
|
||||
}
|
||||
tm := chosen.tsToTime(chosen.dts)
|
||||
if pkt, err = chosen.readPacket(); err != nil {
|
||||
return
|
||||
}
|
||||
pkt.Time = tm
|
||||
pkt.Idx = int8(chosenidx)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) CurrentTime() (tm time.Duration) {
|
||||
if len(self.streams) > 0 {
|
||||
stream := self.streams[0]
|
||||
tm = stream.tsToTime(stream.dts)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) SeekToTime(tm time.Duration) (err error) {
|
||||
for _, stream := range self.streams {
|
||||
if stream.Type().IsVideo() {
|
||||
if err = stream.seekToTime(tm); err != nil {
|
||||
return
|
||||
}
|
||||
tm = stream.tsToTime(stream.dts)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, stream := range self.streams {
|
||||
if !stream.Type().IsVideo() {
|
||||
if err = stream.seekToTime(tm); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) readPacket() (pkt av.Packet, err error) {
|
||||
if !self.isSampleValid() {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
//fmt.Println("readPacket", self.sampleIndex)
|
||||
|
||||
chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex]
|
||||
sampleSize := uint32(0)
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
sampleSize = self.sample.SampleSize.SampleSize
|
||||
} else {
|
||||
sampleSize = self.sample.SampleSize.Entries[self.sampleIndex]
|
||||
}
|
||||
|
||||
sampleOffset := int64(chunkOffset) + self.sampleOffsetInChunk
|
||||
pkt.Data = make([]byte, sampleSize)
|
||||
if err = self.demuxer.readat(sampleOffset, pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
if self.sample.SyncSample.Entries[self.syncSampleIndex]-1 == uint32(self.sampleIndex) {
|
||||
pkt.IsKeyFrame = true
|
||||
}
|
||||
}
|
||||
|
||||
//println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex)
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
cts := int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset)
|
||||
pkt.CompositionTime = self.tsToTime(cts)
|
||||
}
|
||||
|
||||
self.incSampleIndex()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) seekToTime(tm time.Duration) (err error) {
|
||||
index := self.timeToSampleIndex(tm)
|
||||
if err = self.setSampleIndex(index); err != nil {
|
||||
return
|
||||
}
|
||||
if false {
|
||||
fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, tm, self.tsToTime(self.dts))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) timeToSampleIndex(tm time.Duration) int {
|
||||
targetTs := self.timeToTs(tm)
|
||||
targetIndex := 0
|
||||
|
||||
startTs := int64(0)
|
||||
endTs := int64(0)
|
||||
startIndex := 0
|
||||
endIndex := 0
|
||||
found := false
|
||||
for _, entry := range self.sample.TimeToSample.Entries {
|
||||
endTs = startTs + int64(entry.Count*entry.Duration)
|
||||
endIndex = startIndex + int(entry.Count)
|
||||
if targetTs >= startTs && targetTs < endTs {
|
||||
targetIndex = startIndex + int((targetTs-startTs)/int64(entry.Duration))
|
||||
found = true
|
||||
}
|
||||
startTs = endTs
|
||||
startIndex = endIndex
|
||||
}
|
||||
if !found {
|
||||
if targetTs < 0 {
|
||||
targetIndex = 0
|
||||
} else {
|
||||
targetIndex = endIndex - 1
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
entries := self.sample.SyncSample.Entries
|
||||
for i := len(entries) - 1; i >= 0; i-- {
|
||||
if entries[i]-1 < uint32(targetIndex) {
|
||||
targetIndex = int(entries[i] - 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetIndex
|
||||
}
|
||||
32
format/mp4/handler.go
Normal file
32
format/mp4/handler.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/av/avutil"
|
||||
)
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".mp4"
|
||||
|
||||
h.Probe = func(b []byte) bool {
|
||||
switch string(b[4:8]) {
|
||||
case "moov", "ftyp", "free", "mdat", "moof":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r.(io.ReadSeeker))
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w.(io.WriteSeeker))
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
||||
3528
format/mp4/mp4io/atoms.go
Normal file
3528
format/mp4/mp4io/atoms.go
Normal file
File diff suppressed because it is too large
Load Diff
1057
format/mp4/mp4io/gen/gen.go
Normal file
1057
format/mp4/mp4io/gen/gen.go
Normal file
File diff suppressed because it is too large
Load Diff
437
format/mp4/mp4io/gen/pattern.go
Normal file
437
format/mp4/mp4io/gen/pattern.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package main
|
||||
|
||||
func moov_Movie() {
|
||||
atom(Header, MovieHeader)
|
||||
atom(MovieExtend, MovieExtend)
|
||||
atoms(Tracks, Track)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mvhd_MovieHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TimeScale)
|
||||
int32(Duration)
|
||||
fixed32(PreferredRate)
|
||||
fixed16(PreferredVolume)
|
||||
_skip(10)
|
||||
array(Matrix, int32, 9)
|
||||
time32(PreviewTime)
|
||||
time32(PreviewDuration)
|
||||
time32(PosterTime)
|
||||
time32(SelectionTime)
|
||||
time32(SelectionDuration)
|
||||
time32(CurrentTime)
|
||||
int32(NextTrackId)
|
||||
}
|
||||
|
||||
func trak_Track() {
|
||||
atom(Header, TrackHeader)
|
||||
atom(Media, Media)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func tkhd_TrackHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TrackId)
|
||||
_skip(4)
|
||||
int32(Duration)
|
||||
_skip(8)
|
||||
int16(Layer)
|
||||
int16(AlternateGroup)
|
||||
fixed16(Volume)
|
||||
_skip(2)
|
||||
array(Matrix, int32, 9)
|
||||
fixed32(TrackWidth)
|
||||
fixed32(TrackHeight)
|
||||
}
|
||||
|
||||
func hdlr_HandlerRefer() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
bytes(Type, 4)
|
||||
bytes(SubType, 4)
|
||||
bytesleft(Name)
|
||||
}
|
||||
|
||||
func mdia_Media() {
|
||||
atom(Header, MediaHeader)
|
||||
atom(Handler, HandlerRefer)
|
||||
atom(Info, MediaInfo)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mdhd_MediaHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TimeScale)
|
||||
int32(Duration)
|
||||
int16(Language)
|
||||
int16(Quality)
|
||||
}
|
||||
|
||||
func minf_MediaInfo() {
|
||||
atom(Sound, SoundMediaInfo)
|
||||
atom(Video, VideoMediaInfo)
|
||||
atom(Data, DataInfo)
|
||||
atom(Sample, SampleTable)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func dinf_DataInfo() {
|
||||
atom(Refer, DataRefer)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func dref_DataRefer() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int32(_childrenNR)
|
||||
atom(Url, DataReferUrl)
|
||||
}
|
||||
|
||||
func url__DataReferUrl() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
}
|
||||
|
||||
func smhd_SoundMediaInfo() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int16(Balance)
|
||||
_skip(2)
|
||||
}
|
||||
|
||||
func vmhd_VideoMediaInfo() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int16(GraphicsMode)
|
||||
array(Opcolor, int16, 3)
|
||||
}
|
||||
|
||||
func stbl_SampleTable() {
|
||||
atom(SampleDesc, SampleDesc)
|
||||
atom(TimeToSample, TimeToSample)
|
||||
atom(CompositionOffset, CompositionOffset)
|
||||
atom(SampleToChunk, SampleToChunk)
|
||||
atom(SyncSample, SyncSample)
|
||||
atom(ChunkOffset, ChunkOffset)
|
||||
atom(SampleSize, SampleSize)
|
||||
}
|
||||
|
||||
func stsd_SampleDesc() {
|
||||
uint8(Version)
|
||||
_skip(3)
|
||||
int32(_childrenNR)
|
||||
atom(AVC1Desc, AVC1Desc)
|
||||
atom(MP4ADesc, MP4ADesc)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mp4a_MP4ADesc() {
|
||||
_skip(6)
|
||||
int16(DataRefIdx)
|
||||
int16(Version)
|
||||
int16(RevisionLevel)
|
||||
int32(Vendor)
|
||||
int16(NumberOfChannels)
|
||||
int16(SampleSize)
|
||||
int16(CompressionId)
|
||||
_skip(2)
|
||||
fixed32(SampleRate)
|
||||
atom(Conf, ElemStreamDesc)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func avc1_AVC1Desc() {
|
||||
_skip(6)
|
||||
int16(DataRefIdx)
|
||||
int16(Version)
|
||||
int16(Revision)
|
||||
int32(Vendor)
|
||||
int32(TemporalQuality)
|
||||
int32(SpatialQuality)
|
||||
int16(Width)
|
||||
int16(Height)
|
||||
fixed32(HorizontalResolution)
|
||||
fixed32(VorizontalResolution)
|
||||
_skip(4)
|
||||
int16(FrameCount)
|
||||
bytes(CompressorName, 32)
|
||||
int16(Depth)
|
||||
int16(ColorTableId)
|
||||
atom(Conf, AVC1Conf)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func avcC_AVC1Conf() {
|
||||
bytesleft(Data)
|
||||
}
|
||||
|
||||
func stts_TimeToSample() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, TimeToSampleEntry)
|
||||
}
|
||||
|
||||
func TimeToSampleEntry() {
|
||||
uint32(Count)
|
||||
uint32(Duration)
|
||||
}
|
||||
|
||||
func stsc_SampleToChunk() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, SampleToChunkEntry)
|
||||
}
|
||||
|
||||
func SampleToChunkEntry() {
|
||||
uint32(FirstChunk)
|
||||
uint32(SamplesPerChunk)
|
||||
uint32(SampleDescId)
|
||||
}
|
||||
|
||||
func ctts_CompositionOffset() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, CompositionOffsetEntry)
|
||||
}
|
||||
|
||||
func CompositionOffsetEntry() {
|
||||
uint32(Count)
|
||||
uint32(Offset)
|
||||
}
|
||||
|
||||
func stss_SyncSample() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func stco_ChunkOffset() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func moof_MovieFrag() {
|
||||
atom(Header, MovieFragHeader)
|
||||
atoms(Tracks, TrackFrag)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mfhd_MovieFragHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(Seqnum)
|
||||
}
|
||||
|
||||
func traf_TrackFrag() {
|
||||
atom(Header, TrackFragHeader)
|
||||
atom(DecodeTime, TrackFragDecodeTime)
|
||||
atom(Run, TrackFragRun)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mvex_MovieExtend() {
|
||||
atoms(Tracks, TrackExtend)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func trex_TrackExtend() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(TrackId)
|
||||
uint32(DefaultSampleDescIdx)
|
||||
uint32(DefaultSampleDuration)
|
||||
uint32(DefaultSampleSize)
|
||||
uint32(DefaultSampleFlags)
|
||||
}
|
||||
|
||||
func stsz_SampleSize() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(SampleSize)
|
||||
_code(func() {
|
||||
if self.SampleSize != 0 {
|
||||
return
|
||||
}
|
||||
})
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func trun_TrackFragRun() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
|
||||
uint32(DataOffset, _code(func() {
|
||||
if self.Flags&TRUN_DATA_OFFSET != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(FirstSampleFlags, _code(func() {
|
||||
if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
slice(Entries, TrackFragRunEntry, _code(func() {
|
||||
for i, entry := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Duration)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Size)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Flags)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Cts)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}, func() {
|
||||
for i := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}, func() {
|
||||
for i := 0; i < int(_len_Entries); i++ {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
entry := &self.Entries[i]
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
entry.Duration = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
entry.Size = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
entry.Flags = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
entry.Cts = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func TrackFragRunEntry() {
|
||||
uint32(Duration)
|
||||
uint32(Size)
|
||||
uint32(Flags)
|
||||
uint32(Cts)
|
||||
}
|
||||
|
||||
func tfhd_TrackFragHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
|
||||
uint64(BaseDataOffset, _code(func() {
|
||||
if self.Flags&TFHD_BASE_DATA_OFFSET != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(StsdId, _code(func() {
|
||||
if self.Flags&TFHD_STSD_ID != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultDuration, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_DURATION != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultSize, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_SIZE != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultFlags, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_FLAGS != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func tfdt_TrackFragDecodeTime() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time64(Time, _code(func() {
|
||||
if self.Version != 0 {
|
||||
PutTime64(b[n:], self.Time)
|
||||
n += 8
|
||||
} else {
|
||||
PutTime32(b[n:], self.Time)
|
||||
n += 4
|
||||
}
|
||||
}, func() {
|
||||
if self.Version != 0 {
|
||||
n += 8
|
||||
} else {
|
||||
n += 4
|
||||
}
|
||||
}, func() {
|
||||
if self.Version != 0 {
|
||||
self.Time = GetTime64(b[n:])
|
||||
n += 8
|
||||
} else {
|
||||
self.Time = GetTime32(b[n:])
|
||||
n += 4
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
502
format/mp4/mp4io/mp4io.go
Normal file
502
format/mp4/mp4io/mp4io.go
Normal file
@@ -0,0 +1,502 @@
|
||||
package mp4io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
Debug string
|
||||
Offset int
|
||||
prev *ParseError
|
||||
}
|
||||
|
||||
func (self *ParseError) Error() string {
|
||||
s := []string{}
|
||||
for p := self; p != nil; p = p.prev {
|
||||
s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset))
|
||||
}
|
||||
return "mp4io: parse error: " + strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func parseErr(debug string, offset int, prev error) (err error) {
|
||||
_prev, _ := prev.(*ParseError)
|
||||
return &ParseError{Debug: debug, Offset: offset, prev: _prev}
|
||||
}
|
||||
|
||||
func GetTime32(b []byte) (t time.Time) {
|
||||
sec := pio.U32BE(b)
|
||||
t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
t = t.Add(time.Second * time.Duration(sec))
|
||||
return
|
||||
}
|
||||
|
||||
func PutTime32(b []byte, t time.Time) {
|
||||
dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||
sec := uint32(dur / time.Second)
|
||||
pio.PutU32BE(b, sec)
|
||||
}
|
||||
|
||||
func GetTime64(b []byte) (t time.Time) {
|
||||
sec := pio.U64BE(b)
|
||||
t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
t = t.Add(time.Second * time.Duration(sec))
|
||||
return
|
||||
}
|
||||
|
||||
func PutTime64(b []byte, t time.Time) {
|
||||
dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||
sec := uint64(dur / time.Second)
|
||||
pio.PutU64BE(b, sec)
|
||||
}
|
||||
|
||||
func PutFixed16(b []byte, f float64) {
|
||||
intpart, fracpart := math.Modf(f)
|
||||
b[0] = uint8(intpart)
|
||||
b[1] = uint8(fracpart * 256.0)
|
||||
}
|
||||
|
||||
func GetFixed16(b []byte) float64 {
|
||||
return float64(b[0]) + float64(b[1])/256.0
|
||||
}
|
||||
|
||||
func PutFixed32(b []byte, f float64) {
|
||||
intpart, fracpart := math.Modf(f)
|
||||
pio.PutU16BE(b[0:2], uint16(intpart))
|
||||
pio.PutU16BE(b[2:4], uint16(fracpart*65536.0))
|
||||
}
|
||||
|
||||
func GetFixed32(b []byte) float64 {
|
||||
return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0
|
||||
}
|
||||
|
||||
type Tag uint32
|
||||
|
||||
func (self Tag) String() string {
|
||||
var b [4]byte
|
||||
pio.PutU32BE(b[:], uint32(self))
|
||||
for i := 0; i < 4; i++ {
|
||||
if b[i] == 0 {
|
||||
b[i] = ' '
|
||||
}
|
||||
}
|
||||
return string(b[:])
|
||||
}
|
||||
|
||||
type Atom interface {
|
||||
Pos() (int, int)
|
||||
Tag() Tag
|
||||
Marshal([]byte) int
|
||||
Unmarshal([]byte, int) (int, error)
|
||||
Len() int
|
||||
Children() []Atom
|
||||
}
|
||||
|
||||
type AtomPos struct {
|
||||
Offset int
|
||||
Size int
|
||||
}
|
||||
|
||||
func (self AtomPos) Pos() (int, int) {
|
||||
return self.Offset, self.Size
|
||||
}
|
||||
|
||||
func (self *AtomPos) setPos(offset int, size int) {
|
||||
self.Offset, self.Size = offset, size
|
||||
}
|
||||
|
||||
type Dummy struct {
|
||||
Data []byte
|
||||
Tag_ Tag
|
||||
AtomPos
|
||||
}
|
||||
|
||||
func (self Dummy) Children() []Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self Dummy) Tag() Tag {
|
||||
return self.Tag_
|
||||
}
|
||||
|
||||
func (self Dummy) Len() int {
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self Dummy) Marshal(b []byte) int {
|
||||
copy(b, self.Data)
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
(&self.AtomPos).setPos(offset, len(b))
|
||||
self.Data = b
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func StringToTag(tag string) Tag {
|
||||
var b [4]byte
|
||||
copy(b[:], []byte(tag))
|
||||
return Tag(pio.U32BE(b[:]))
|
||||
}
|
||||
|
||||
func FindChildrenByName(root Atom, tag string) Atom {
|
||||
return FindChildren(root, StringToTag(tag))
|
||||
}
|
||||
|
||||
func FindChildren(root Atom, tag Tag) Atom {
|
||||
if root.Tag() == tag {
|
||||
return root
|
||||
}
|
||||
for _, child := range root.Children() {
|
||||
if r := FindChildren(child, tag); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
TFHD_BASE_DATA_OFFSET = 0x01
|
||||
TFHD_STSD_ID = 0x02
|
||||
TFHD_DEFAULT_DURATION = 0x08
|
||||
TFHD_DEFAULT_SIZE = 0x10
|
||||
TFHD_DEFAULT_FLAGS = 0x20
|
||||
TFHD_DURATION_IS_EMPTY = 0x010000
|
||||
TFHD_DEFAULT_BASE_IS_MOOF = 0x020000
|
||||
)
|
||||
|
||||
const (
|
||||
TRUN_DATA_OFFSET = 0x01
|
||||
TRUN_FIRST_SAMPLE_FLAGS = 0x04
|
||||
TRUN_SAMPLE_DURATION = 0x100
|
||||
TRUN_SAMPLE_SIZE = 0x200
|
||||
TRUN_SAMPLE_FLAGS = 0x400
|
||||
TRUN_SAMPLE_CTS = 0x800
|
||||
)
|
||||
|
||||
const (
|
||||
MP4ESDescrTag = 3
|
||||
MP4DecConfigDescrTag = 4
|
||||
MP4DecSpecificDescrTag = 5
|
||||
)
|
||||
|
||||
type ElemStreamDesc struct {
|
||||
DecConfig []byte
|
||||
TrackId uint16
|
||||
AtomPos
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Children() []Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) {
|
||||
for i := 3; i > 0; i-- {
|
||||
b[n] = uint8(length>>uint(7*i))&0x7f | 0x80
|
||||
n++
|
||||
}
|
||||
b[n] = uint8(length & 0x7f)
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDescHdr() (n int) {
|
||||
return 5
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) {
|
||||
b[n] = tag
|
||||
n++
|
||||
n += self.fillLength(b[n:], datalen)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenESDescHdr() (n int) {
|
||||
return self.lenDescHdr() + 3
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen)
|
||||
pio.PutU16BE(b[n:], self.TrackId)
|
||||
n += 2
|
||||
b[n] = 0 // flags
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) {
|
||||
return self.lenDescHdr() + 2 + 3 + 4 + 4 + self.lenDescHdr()
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen)
|
||||
b[n] = 0x40 // objectid
|
||||
n++
|
||||
b[n] = 0x15 // streamtype
|
||||
n++
|
||||
// buffer size db
|
||||
pio.PutU24BE(b[n:], 0)
|
||||
n += 3
|
||||
// max bitrage
|
||||
pio.PutU32BE(b[n:], uint32(200000))
|
||||
n += 4
|
||||
// avg bitrage
|
||||
pio.PutU32BE(b[n:], uint32(0))
|
||||
n += 4
|
||||
n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Len() (n int) {
|
||||
return 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1
|
||||
}
|
||||
|
||||
// Version(4)
|
||||
// ESDesc(
|
||||
// MP4ESDescrTag
|
||||
// ESID(2)
|
||||
// ESFlags(1)
|
||||
// DecConfigDesc(
|
||||
// MP4DecConfigDescrTag
|
||||
// objectId streamType bufSize avgBitrate
|
||||
// DecSpecificDesc(
|
||||
// MP4DecSpecificDescrTag
|
||||
// decConfig
|
||||
// )
|
||||
// )
|
||||
// ?Desc(lenDescHdr+1)
|
||||
// )
|
||||
|
||||
func (self ElemStreamDesc) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(ESDS))
|
||||
n += 8
|
||||
pio.PutU32BE(b[n:], 0) // Version
|
||||
n += 4
|
||||
datalen := self.Len()
|
||||
n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr())
|
||||
n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-1)
|
||||
copy(b[n:], self.DecConfig)
|
||||
n += len(self.DecConfig)
|
||||
n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr())
|
||||
b[n] = 0x02
|
||||
n++
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
if len(b) < n+12 {
|
||||
err = parseErr("hdr", offset+n, err)
|
||||
return
|
||||
}
|
||||
(&self.AtomPos).setPos(offset, len(b))
|
||||
n += 8
|
||||
n += 4
|
||||
return self.parseDesc(b[n:], offset+n)
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) {
|
||||
var hdrlen int
|
||||
var datalen int
|
||||
var tag uint8
|
||||
if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil {
|
||||
return
|
||||
}
|
||||
n += hdrlen
|
||||
|
||||
if len(b) < n+datalen {
|
||||
err = parseErr("datalen", offset+n, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case MP4ESDescrTag:
|
||||
if len(b) < n+3 {
|
||||
err = parseErr("MP4ESDescrTag", offset+n, err)
|
||||
return
|
||||
}
|
||||
if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MP4DecConfigDescrTag:
|
||||
const size = 2 + 3 + 4 + 4
|
||||
if len(b) < n+size {
|
||||
err = parseErr("MP4DecSpecificDescrTag", offset+n, err)
|
||||
return
|
||||
}
|
||||
if _, err = self.parseDesc(b[n+size:], offset+n+size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MP4DecSpecificDescrTag:
|
||||
self.DecConfig = b[n:]
|
||||
}
|
||||
|
||||
n += datalen
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) {
|
||||
for n < 4 {
|
||||
if len(b) < n+1 {
|
||||
err = parseErr("len", offset+n, err)
|
||||
return
|
||||
}
|
||||
c := b[n]
|
||||
n++
|
||||
length = (length << 7) | (int(c) & 0x7f)
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = parseErr("tag", offset+n, err)
|
||||
return
|
||||
}
|
||||
tag = b[n]
|
||||
n++
|
||||
var lenlen int
|
||||
if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil {
|
||||
return
|
||||
}
|
||||
n += lenlen
|
||||
return
|
||||
}
|
||||
|
||||
func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) {
|
||||
for {
|
||||
offset, _ := r.Seek(0, 1)
|
||||
taghdr := make([]byte, 8)
|
||||
if _, err = io.ReadFull(r, taghdr); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
size := pio.U32BE(taghdr[0:])
|
||||
tag := Tag(pio.U32BE(taghdr[4:]))
|
||||
|
||||
var atom Atom
|
||||
switch tag {
|
||||
case MOOV:
|
||||
atom = &Movie{}
|
||||
case MOOF:
|
||||
atom = &MovieFrag{}
|
||||
}
|
||||
|
||||
if atom != nil {
|
||||
b := make([]byte, int(size))
|
||||
if _, err = io.ReadFull(r, b[8:]); err != nil {
|
||||
return
|
||||
}
|
||||
copy(b, taghdr)
|
||||
if _, err = atom.Unmarshal(b, int(offset)); err != nil {
|
||||
return
|
||||
}
|
||||
atoms = append(atoms, atom)
|
||||
} else {
|
||||
dummy := &Dummy{Tag_: tag}
|
||||
dummy.setPos(int(offset), int(size))
|
||||
if _, err = r.Seek(int64(size)-8, 1); err != nil {
|
||||
return
|
||||
}
|
||||
atoms = append(atoms, dummy)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func printatom(out io.Writer, root Atom, depth int) {
|
||||
offset, size := root.Pos()
|
||||
|
||||
type stringintf interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
fmt.Fprintf(out,
|
||||
"%s%s offset=%d size=%d",
|
||||
strings.Repeat(" ", depth*2), root.Tag(), offset, size,
|
||||
)
|
||||
if str, ok := root.(stringintf); ok {
|
||||
fmt.Fprint(out, " ", str.String())
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
|
||||
children := root.Children()
|
||||
for _, child := range children {
|
||||
printatom(out, child, depth+1)
|
||||
}
|
||||
}
|
||||
|
||||
func FprintAtom(out io.Writer, root Atom) {
|
||||
printatom(out, root, 0)
|
||||
}
|
||||
|
||||
func PrintAtom(root Atom) {
|
||||
FprintAtom(os.Stdout, root)
|
||||
}
|
||||
|
||||
func (self MovieHeader) String() string {
|
||||
return fmt.Sprintf("dur=%d", self.Duration)
|
||||
}
|
||||
|
||||
func (self TimeToSample) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SampleToChunk) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SampleSize) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SyncSample) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self CompositionOffset) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self ChunkOffset) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self TrackFragRun) String() string {
|
||||
return fmt.Sprintf("dataoffset=%d", self.DataOffset)
|
||||
}
|
||||
|
||||
func (self TrackFragHeader) String() string {
|
||||
return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset)
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) String() string {
|
||||
return fmt.Sprintf("configlen=%d", len(self.DecConfig))
|
||||
}
|
||||
|
||||
func (self *Track) GetAVC1Conf() (conf *AVC1Conf) {
|
||||
atom := FindChildren(self, AVCC)
|
||||
conf, _ = atom.(*AVC1Conf)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) {
|
||||
atom := FindChildren(self, ESDS)
|
||||
esds, _ = atom.(*ElemStreamDesc)
|
||||
return
|
||||
}
|
||||
283
format/mp4/muxer.go
Normal file
283
format/mp4/muxer.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type Muxer struct {
|
||||
w io.WriteSeeker
|
||||
bufw *bufio.Writer
|
||||
wpos int64
|
||||
streams []*Stream
|
||||
}
|
||||
|
||||
func NewMuxer(w io.WriteSeeker) *Muxer {
|
||||
return &Muxer{
|
||||
w: w,
|
||||
bufw: bufio.NewWriterSize(w, pio.RecommendBufioSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||
switch codec.Type() {
|
||||
case av.H264, av.AAC:
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("mp4: codec type=%v is not supported", codec.Type())
|
||||
return
|
||||
}
|
||||
stream := &Stream{CodecData: codec}
|
||||
|
||||
stream.sample = &mp4io.SampleTable{
|
||||
SampleDesc: &mp4io.SampleDesc{},
|
||||
TimeToSample: &mp4io.TimeToSample{},
|
||||
SampleToChunk: &mp4io.SampleToChunk{
|
||||
Entries: []mp4io.SampleToChunkEntry{
|
||||
{
|
||||
FirstChunk: 1,
|
||||
SampleDescId: 1,
|
||||
SamplesPerChunk: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
SampleSize: &mp4io.SampleSize{},
|
||||
ChunkOffset: &mp4io.ChunkOffset{},
|
||||
}
|
||||
|
||||
stream.trackAtom = &mp4io.Track{
|
||||
Header: &mp4io.TrackHeader{
|
||||
TrackId: int32(len(self.streams) + 1),
|
||||
Flags: 0x0003, // Track enabled | Track in movie
|
||||
Duration: 0, // fill later
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
},
|
||||
Media: &mp4io.Media{
|
||||
Header: &mp4io.MediaHeader{
|
||||
TimeScale: 0, // fill later
|
||||
Duration: 0, // fill later
|
||||
Language: 21956,
|
||||
},
|
||||
Info: &mp4io.MediaInfo{
|
||||
Sample: stream.sample,
|
||||
Data: &mp4io.DataInfo{
|
||||
Refer: &mp4io.DataRefer{
|
||||
Url: &mp4io.DataReferUrl{
|
||||
Flags: 0x000001, // Self reference
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch codec.Type() {
|
||||
case av.H264:
|
||||
stream.sample.SyncSample = &mp4io.SyncSample{}
|
||||
}
|
||||
|
||||
stream.timeScale = 90000
|
||||
stream.muxer = self
|
||||
self.streams = append(self.streams, stream)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) fillTrackAtom() (err error) {
|
||||
self.trackAtom.Media.Header.TimeScale = int32(self.timeScale)
|
||||
self.trackAtom.Media.Header.Duration = int32(self.duration)
|
||||
|
||||
if self.Type() == av.H264 {
|
||||
codec := self.CodecData.(h264parser.CodecData)
|
||||
width, height := codec.Width(), codec.Height()
|
||||
self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{
|
||||
DataRefIdx: 1,
|
||||
HorizontalResolution: 72,
|
||||
VorizontalResolution: 72,
|
||||
Width: int16(width),
|
||||
Height: int16(height),
|
||||
FrameCount: 1,
|
||||
Depth: 24,
|
||||
ColorTableId: -1,
|
||||
Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()},
|
||||
}
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'v', 'i', 'd', 'e'},
|
||||
Name: []byte("Video Media Handler"),
|
||||
}
|
||||
self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{
|
||||
Flags: 0x000001,
|
||||
}
|
||||
self.trackAtom.Header.TrackWidth = float64(width)
|
||||
self.trackAtom.Header.TrackHeight = float64(height)
|
||||
|
||||
} else if self.Type() == av.AAC {
|
||||
codec := self.CodecData.(aacparser.CodecData)
|
||||
self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{
|
||||
DataRefIdx: 1,
|
||||
NumberOfChannels: int16(codec.ChannelLayout().Count()),
|
||||
SampleSize: int16(codec.SampleFormat().BytesPerSample()),
|
||||
SampleRate: float64(codec.SampleRate()),
|
||||
Conf: &mp4io.ElemStreamDesc{
|
||||
DecConfig: codec.MPEG4AudioConfigBytes(),
|
||||
},
|
||||
}
|
||||
self.trackAtom.Header.Volume = 1
|
||||
self.trackAtom.Header.AlternateGroup = 1
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'s', 'o', 'u', 'n'},
|
||||
Name: []byte("Sound Handler"),
|
||||
}
|
||||
self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}
|
||||
|
||||
} else {
|
||||
err = fmt.Errorf("mp4: codec type=%d invalid", self.Type())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
self.streams = []*Stream{}
|
||||
for _, stream := range streams {
|
||||
if err = self.newStream(stream); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
taghdr := make([]byte, 8)
|
||||
pio.PutU32BE(taghdr[4:], uint32(mp4io.MDAT))
|
||||
if _, err = self.w.Write(taghdr); err != nil {
|
||||
return
|
||||
}
|
||||
self.wpos += 8
|
||||
|
||||
for _, stream := range self.streams {
|
||||
if stream.Type().IsVideo() {
|
||||
stream.sample.CompositionOffset = &mp4io.CompositionOffset{}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
if stream.lastpkt != nil {
|
||||
if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
stream.lastpkt = &pkt
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) writePacket(pkt av.Packet, rawdur time.Duration) (err error) {
|
||||
if rawdur < 0 {
|
||||
err = fmt.Errorf("mp4: stream#%d time=%v < lasttime=%v", pkt.Idx, pkt.Time, self.lastpkt.Time)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = self.muxer.bufw.Write(pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pkt.IsKeyFrame && self.sample.SyncSample != nil {
|
||||
self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, uint32(self.sampleIndex+1))
|
||||
}
|
||||
|
||||
duration := uint32(self.timeToTs(rawdur))
|
||||
if self.sttsEntry == nil || duration != self.sttsEntry.Duration {
|
||||
self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, mp4io.TimeToSampleEntry{Duration: duration})
|
||||
self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1]
|
||||
}
|
||||
self.sttsEntry.Count++
|
||||
|
||||
if self.sample.CompositionOffset != nil {
|
||||
offset := uint32(self.timeToTs(pkt.CompositionTime))
|
||||
if self.cttsEntry == nil || offset != self.cttsEntry.Offset {
|
||||
table := self.sample.CompositionOffset
|
||||
table.Entries = append(table.Entries, mp4io.CompositionOffsetEntry{Offset: offset})
|
||||
self.cttsEntry = &table.Entries[len(table.Entries)-1]
|
||||
}
|
||||
self.cttsEntry.Count++
|
||||
}
|
||||
|
||||
self.duration += int64(duration)
|
||||
self.sampleIndex++
|
||||
self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, uint32(self.muxer.wpos))
|
||||
self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, uint32(len(pkt.Data)))
|
||||
|
||||
self.muxer.wpos += int64(len(pkt.Data))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
for _, stream := range self.streams {
|
||||
if stream.lastpkt != nil {
|
||||
if err = stream.writePacket(*stream.lastpkt, 0); err != nil {
|
||||
return
|
||||
}
|
||||
stream.lastpkt = nil
|
||||
}
|
||||
}
|
||||
|
||||
moov := &mp4io.Movie{}
|
||||
moov.Header = &mp4io.MovieHeader{
|
||||
PreferredRate: 1,
|
||||
PreferredVolume: 1,
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
NextTrackId: 2,
|
||||
}
|
||||
|
||||
maxDur := time.Duration(0)
|
||||
timeScale := int64(10000)
|
||||
for _, stream := range self.streams {
|
||||
if err = stream.fillTrackAtom(); err != nil {
|
||||
return
|
||||
}
|
||||
dur := stream.tsToTime(stream.duration)
|
||||
stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale))
|
||||
if dur > maxDur {
|
||||
maxDur = dur
|
||||
}
|
||||
moov.Tracks = append(moov.Tracks, stream.trackAtom)
|
||||
}
|
||||
moov.Header.TimeScale = int32(timeScale)
|
||||
moov.Header.Duration = int32(timeToTs(maxDur, timeScale))
|
||||
|
||||
if err = self.bufw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var mdatsize int64
|
||||
if mdatsize, err = self.w.Seek(0, 1); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.w.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
taghdr := make([]byte, 4)
|
||||
pio.PutU32BE(taghdr, uint32(mdatsize))
|
||||
if _, err = self.w.Write(taghdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = self.w.Seek(0, 2); err != nil {
|
||||
return
|
||||
}
|
||||
b := make([]byte, moov.Len())
|
||||
moov.Marshal(b)
|
||||
if _, err = self.w.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
59
format/mp4/stream.go
Normal file
59
format/mp4/stream.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
|
||||
trackAtom *mp4io.Track
|
||||
idx int
|
||||
|
||||
lastpkt *av.Packet
|
||||
|
||||
timeScale int64
|
||||
duration int64
|
||||
|
||||
muxer *Muxer
|
||||
demuxer *Demuxer
|
||||
|
||||
sample *mp4io.SampleTable
|
||||
sampleIndex int
|
||||
|
||||
sampleOffsetInChunk int64
|
||||
syncSampleIndex int
|
||||
|
||||
dts int64
|
||||
sttsEntryIndex int
|
||||
sampleIndexInSttsEntry int
|
||||
|
||||
cttsEntryIndex int
|
||||
sampleIndexInCttsEntry int
|
||||
|
||||
chunkGroupIndex int
|
||||
chunkIndex int
|
||||
sampleIndexInChunk int
|
||||
|
||||
sttsEntry *mp4io.TimeToSampleEntry
|
||||
cttsEntry *mp4io.CompositionOffsetEntry
|
||||
}
|
||||
|
||||
func timeToTs(tm time.Duration, timeScale int64) int64 {
|
||||
return int64(tm * time.Duration(timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func tsToTime(ts int64, timeScale int64) time.Duration {
|
||||
return time.Duration(ts) * time.Second / time.Duration(timeScale)
|
||||
}
|
||||
|
||||
func (self *Stream) timeToTs(tm time.Duration) int64 {
|
||||
return int64(tm * time.Duration(self.timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func (self *Stream) tsToTime(ts int64) time.Duration {
|
||||
return time.Duration(ts) * time.Second / time.Duration(self.timeScale)
|
||||
}
|
||||
30
format/mp4f/fd.go
Normal file
30
format/mp4f/fd.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package mp4f
|
||||
|
||||
import "github.com/deepch/vdk/format/mp4/mp4io"
|
||||
|
||||
type FDummy struct {
|
||||
Data []byte
|
||||
Tag_ mp4io.Tag
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self FDummy) Children() []mp4io.Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self FDummy) Tag() mp4io.Tag {
|
||||
return self.Tag_
|
||||
}
|
||||
|
||||
func (self FDummy) Len() int {
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self FDummy) Marshal(b []byte) int {
|
||||
copy(b, self.Data)
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self FDummy) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
return
|
||||
}
|
||||
356
format/mp4f/mp4fio/atoms.go
Normal file
356
format/mp4f/mp4fio/atoms.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package mp4fio
|
||||
|
||||
import (
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
func (self MovieFrag) Tag() mp4io.Tag {
|
||||
return mp4io.MOOF
|
||||
}
|
||||
|
||||
type MovieFrag struct {
|
||||
Header *MovieFragHeader
|
||||
Tracks []*TrackFrag
|
||||
Unknowns []mp4io.Atom
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self MovieFrag) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.MOOF))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self MovieFrag) marshal(b []byte) (n int) {
|
||||
if self.Header != nil {
|
||||
n += self.Header.Marshal(b[n:])
|
||||
}
|
||||
for _, atom := range self.Tracks {
|
||||
n += atom.Marshal(b[n:])
|
||||
}
|
||||
for _, atom := range self.Unknowns {
|
||||
n += atom.Marshal(b[n:])
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self MovieFrag) Len() (n int) {
|
||||
n += 8
|
||||
if self.Header != nil {
|
||||
n += self.Header.Len()
|
||||
}
|
||||
for _, atom := range self.Tracks {
|
||||
n += atom.Len()
|
||||
}
|
||||
for _, atom := range self.Unknowns {
|
||||
n += atom.Len()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self MovieFrag) Children() (r []mp4io.Atom) {
|
||||
if self.Header != nil {
|
||||
r = append(r, self.Header)
|
||||
}
|
||||
for _, atom := range self.Tracks {
|
||||
r = append(r, atom)
|
||||
}
|
||||
r = append(r, self.Unknowns...)
|
||||
return
|
||||
}
|
||||
|
||||
func (self MovieFragHeader) Tag() mp4io.Tag {
|
||||
return mp4io.MFHD
|
||||
}
|
||||
|
||||
type MovieFragHeader struct {
|
||||
Version uint8
|
||||
Flags uint32
|
||||
Seqnum uint32
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self MovieFragHeader) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.MFHD))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self MovieFragHeader) marshal(b []byte) (n int) {
|
||||
pio.PutU8(b[n:], self.Version)
|
||||
n += 1
|
||||
pio.PutU24BE(b[n:], self.Flags)
|
||||
n += 3
|
||||
pio.PutU32BE(b[n:], self.Seqnum)
|
||||
n += 4
|
||||
return
|
||||
}
|
||||
func (self MovieFragHeader) Len() (n int) {
|
||||
n += 8
|
||||
n += 1
|
||||
n += 3
|
||||
n += 4
|
||||
return
|
||||
}
|
||||
func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self MovieFragHeader) Children() (r []mp4io.Atom) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self TrackFragRun) Tag() mp4io.Tag {
|
||||
return mp4io.TRUN
|
||||
}
|
||||
|
||||
type TrackFragRun struct {
|
||||
Version uint8
|
||||
Flags uint32
|
||||
DataOffset uint32
|
||||
FirstSampleFlags uint32
|
||||
Entries []mp4io.TrackFragRunEntry
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self TrackFragRun) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.TRUN))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self TrackFragRun) marshal(b []byte) (n int) {
|
||||
pio.PutU8(b[n:], self.Version)
|
||||
n += 1
|
||||
pio.PutU24BE(b[n:], self.Flags)
|
||||
n += 3
|
||||
pio.PutU32BE(b[n:], uint32(len(self.Entries)))
|
||||
n += 4
|
||||
if self.Flags&mp4io.TRUN_DATA_OFFSET != 0 {
|
||||
{
|
||||
pio.PutU32BE(b[n:], self.DataOffset)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
if self.Flags&mp4io.TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||
{
|
||||
pio.PutU32BE(b[n:], self.FirstSampleFlags)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
|
||||
for i, entry := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
//if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Duration)
|
||||
n += 4
|
||||
//}
|
||||
//if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Size)
|
||||
n += 4
|
||||
//}
|
||||
if flags&mp4io.TRUN_SAMPLE_FLAGS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Flags)
|
||||
n += 4
|
||||
}
|
||||
//if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Cts)
|
||||
n += 4
|
||||
//}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self TrackFragRun) Len() (n int) {
|
||||
n += 8
|
||||
n += 1
|
||||
n += 3
|
||||
n += 4
|
||||
if self.Flags&mp4io.TRUN_DATA_OFFSET != 0 {
|
||||
{
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
if self.Flags&mp4io.TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||
{
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
|
||||
for i := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
//if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
n += 4
|
||||
//}
|
||||
//if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
n += 4
|
||||
//}
|
||||
if flags&mp4io.TRUN_SAMPLE_FLAGS != 0 {
|
||||
n += 4
|
||||
}
|
||||
//if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
n += 4
|
||||
//}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self TrackFragRun) Children() (r []mp4io.Atom) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self TrackFrag) Tag() mp4io.Tag {
|
||||
return mp4io.TRAF
|
||||
}
|
||||
|
||||
type TrackFrag struct {
|
||||
Header *TrackFragHeader
|
||||
DecodeTime *TrackFragDecodeTime
|
||||
Run *TrackFragRun
|
||||
Unknowns []mp4io.Atom
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self TrackFrag) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.TRAF))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self TrackFrag) marshal(b []byte) (n int) {
|
||||
if self.Header != nil {
|
||||
n += self.Header.Marshal(b[n:])
|
||||
}
|
||||
if self.DecodeTime != nil {
|
||||
n += self.DecodeTime.Marshal(b[n:])
|
||||
}
|
||||
if self.Run != nil {
|
||||
n += self.Run.Marshal(b[n:])
|
||||
}
|
||||
for _, atom := range self.Unknowns {
|
||||
n += atom.Marshal(b[n:])
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self TrackFrag) Len() (n int) {
|
||||
n += 8
|
||||
if self.Header != nil {
|
||||
n += self.Header.Len()
|
||||
}
|
||||
if self.DecodeTime != nil {
|
||||
n += self.DecodeTime.Len()
|
||||
}
|
||||
if self.Run != nil {
|
||||
n += self.Run.Len()
|
||||
}
|
||||
for _, atom := range self.Unknowns {
|
||||
n += atom.Len()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self TrackFrag) Children() (r []mp4io.Atom) {
|
||||
if self.Header != nil {
|
||||
r = append(r, self.Header)
|
||||
}
|
||||
if self.DecodeTime != nil {
|
||||
r = append(r, self.DecodeTime)
|
||||
}
|
||||
if self.Run != nil {
|
||||
r = append(r, self.Run)
|
||||
}
|
||||
r = append(r, self.Unknowns...)
|
||||
return
|
||||
}
|
||||
|
||||
const LenTrackFragRunEntry = 16
|
||||
|
||||
func (self TrackFragHeader) Tag() mp4io.Tag {
|
||||
return mp4io.TFHD
|
||||
}
|
||||
|
||||
type TrackFragHeader struct {
|
||||
Data []byte
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self TrackFragHeader) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.TFHD))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self TrackFragHeader) marshal(b []byte) (n int) {
|
||||
copy(b, self.Data)
|
||||
n += len(self.Data)
|
||||
return
|
||||
}
|
||||
func (self TrackFragHeader) Len() (n int) {
|
||||
return len(self.Data) + 8
|
||||
}
|
||||
func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self TrackFragHeader) Children() (r []mp4io.Atom) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self TrackFragDecodeTime) Tag() mp4io.Tag {
|
||||
return mp4io.TFDT
|
||||
}
|
||||
|
||||
type TrackFragDecodeTime struct {
|
||||
Version uint8
|
||||
Flags uint32
|
||||
Time uint64
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self TrackFragDecodeTime) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.TFDT))
|
||||
n += self.marshal(b[8:]) + 8
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
func (self TrackFragDecodeTime) marshal(b []byte) (n int) {
|
||||
pio.PutU8(b[n:], self.Version)
|
||||
n += 1
|
||||
pio.PutU24BE(b[n:], self.Flags)
|
||||
n += 3
|
||||
pio.PutU64BE(b[n:], self.Time)
|
||||
n += 8
|
||||
return
|
||||
}
|
||||
func (self TrackFragDecodeTime) Len() (n int) {
|
||||
n += 8
|
||||
n += 1
|
||||
n += 3
|
||||
n += 8
|
||||
return
|
||||
}
|
||||
func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
func (self TrackFragDecodeTime) Children() (r []mp4io.Atom) {
|
||||
return
|
||||
}
|
||||
1057
format/mp4f/mp4fio/gen/gen.go
Normal file
1057
format/mp4f/mp4fio/gen/gen.go
Normal file
File diff suppressed because it is too large
Load Diff
437
format/mp4f/mp4fio/gen/pattern.go
Normal file
437
format/mp4f/mp4fio/gen/pattern.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package main
|
||||
|
||||
func moov_Movie() {
|
||||
atom(Header, MovieHeader)
|
||||
atom(MovieExtend, MovieExtend)
|
||||
atoms(Tracks, Track)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mvhd_MovieHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TimeScale)
|
||||
int32(Duration)
|
||||
fixed32(PreferredRate)
|
||||
fixed16(PreferredVolume)
|
||||
_skip(10)
|
||||
array(Matrix, int32, 9)
|
||||
time32(PreviewTime)
|
||||
time32(PreviewDuration)
|
||||
time32(PosterTime)
|
||||
time32(SelectionTime)
|
||||
time32(SelectionDuration)
|
||||
time32(CurrentTime)
|
||||
int32(NextTrackId)
|
||||
}
|
||||
|
||||
func trak_Track() {
|
||||
atom(Header, TrackHeader)
|
||||
atom(Media, Media)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func tkhd_TrackHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TrackId)
|
||||
_skip(4)
|
||||
int32(Duration)
|
||||
_skip(8)
|
||||
int16(Layer)
|
||||
int16(AlternateGroup)
|
||||
fixed16(Volume)
|
||||
_skip(2)
|
||||
array(Matrix, int32, 9)
|
||||
fixed32(TrackWidth)
|
||||
fixed32(TrackHeight)
|
||||
}
|
||||
|
||||
func hdlr_HandlerRefer() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
bytes(Type, 4)
|
||||
bytes(SubType, 4)
|
||||
bytesleft(Name)
|
||||
}
|
||||
|
||||
func mdia_Media() {
|
||||
atom(Header, MediaHeader)
|
||||
atom(Handler, HandlerRefer)
|
||||
atom(Info, MediaInfo)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mdhd_MediaHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time32(CreateTime)
|
||||
time32(ModifyTime)
|
||||
int32(TimeScale)
|
||||
int32(Duration)
|
||||
int16(Language)
|
||||
int16(Quality)
|
||||
}
|
||||
|
||||
func minf_MediaInfo() {
|
||||
atom(Sound, SoundMediaInfo)
|
||||
atom(Video, VideoMediaInfo)
|
||||
atom(Data, DataInfo)
|
||||
atom(Sample, SampleTable)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func dinf_DataInfo() {
|
||||
atom(Refer, DataRefer)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func dref_DataRefer() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int32(_childrenNR)
|
||||
atom(Url, DataReferUrl)
|
||||
}
|
||||
|
||||
func url__DataReferUrl() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
}
|
||||
|
||||
func smhd_SoundMediaInfo() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int16(Balance)
|
||||
_skip(2)
|
||||
}
|
||||
|
||||
func vmhd_VideoMediaInfo() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
int16(GraphicsMode)
|
||||
array(Opcolor, int16, 3)
|
||||
}
|
||||
|
||||
func stbl_SampleTable() {
|
||||
atom(SampleDesc, SampleDesc)
|
||||
atom(TimeToSample, TimeToSample)
|
||||
atom(CompositionOffset, CompositionOffset)
|
||||
atom(SampleToChunk, SampleToChunk)
|
||||
atom(SyncSample, SyncSample)
|
||||
atom(ChunkOffset, ChunkOffset)
|
||||
atom(SampleSize, SampleSize)
|
||||
}
|
||||
|
||||
func stsd_SampleDesc() {
|
||||
uint8(Version)
|
||||
_skip(3)
|
||||
int32(_childrenNR)
|
||||
atom(AVC1Desc, AVC1Desc)
|
||||
atom(MP4ADesc, MP4ADesc)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mp4a_MP4ADesc() {
|
||||
_skip(6)
|
||||
int16(DataRefIdx)
|
||||
int16(Version)
|
||||
int16(RevisionLevel)
|
||||
int32(Vendor)
|
||||
int16(NumberOfChannels)
|
||||
int16(SampleSize)
|
||||
int16(CompressionId)
|
||||
_skip(2)
|
||||
fixed32(SampleRate)
|
||||
atom(Conf, ElemStreamDesc)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func avc1_AVC1Desc() {
|
||||
_skip(6)
|
||||
int16(DataRefIdx)
|
||||
int16(Version)
|
||||
int16(Revision)
|
||||
int32(Vendor)
|
||||
int32(TemporalQuality)
|
||||
int32(SpatialQuality)
|
||||
int16(Width)
|
||||
int16(Height)
|
||||
fixed32(HorizontalResolution)
|
||||
fixed32(VorizontalResolution)
|
||||
_skip(4)
|
||||
int16(FrameCount)
|
||||
bytes(CompressorName, 32)
|
||||
int16(Depth)
|
||||
int16(ColorTableId)
|
||||
atom(Conf, AVC1Conf)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func avcC_AVC1Conf() {
|
||||
bytesleft(Data)
|
||||
}
|
||||
|
||||
func stts_TimeToSample() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, TimeToSampleEntry)
|
||||
}
|
||||
|
||||
func TimeToSampleEntry() {
|
||||
uint32(Count)
|
||||
uint32(Duration)
|
||||
}
|
||||
|
||||
func stsc_SampleToChunk() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, SampleToChunkEntry)
|
||||
}
|
||||
|
||||
func SampleToChunkEntry() {
|
||||
uint32(FirstChunk)
|
||||
uint32(SamplesPerChunk)
|
||||
uint32(SampleDescId)
|
||||
}
|
||||
|
||||
func ctts_CompositionOffset() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, CompositionOffsetEntry)
|
||||
}
|
||||
|
||||
func CompositionOffsetEntry() {
|
||||
uint32(Count)
|
||||
uint32(Offset)
|
||||
}
|
||||
|
||||
func stss_SyncSample() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func stco_ChunkOffset() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func moof_MovieFrag() {
|
||||
atom(Header, MovieFragHeader)
|
||||
atoms(Tracks, TrackFrag)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mfhd_MovieFragHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(Seqnum)
|
||||
}
|
||||
|
||||
func traf_TrackFrag() {
|
||||
atom(Header, TrackFragHeader)
|
||||
atom(DecodeTime, TrackFragDecodeTime)
|
||||
atom(Run, TrackFragRun)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func mvex_MovieExtend() {
|
||||
atoms(Tracks, TrackExtend)
|
||||
_unknowns()
|
||||
}
|
||||
|
||||
func trex_TrackExtend() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(TrackId)
|
||||
uint32(DefaultSampleDescIdx)
|
||||
uint32(DefaultSampleDuration)
|
||||
uint32(DefaultSampleSize)
|
||||
uint32(DefaultSampleFlags)
|
||||
}
|
||||
|
||||
func stsz_SampleSize() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(SampleSize)
|
||||
_code(func() {
|
||||
if self.SampleSize != 0 {
|
||||
return
|
||||
}
|
||||
})
|
||||
uint32(_len_Entries)
|
||||
slice(Entries, uint32)
|
||||
}
|
||||
|
||||
func trun_TrackFragRun() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
uint32(_len_Entries)
|
||||
|
||||
uint32(DataOffset, _code(func() {
|
||||
if self.Flags&TRUN_DATA_OFFSET != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(FirstSampleFlags, _code(func() {
|
||||
if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
slice(Entries, TrackFragRunEntry, _code(func() {
|
||||
for i, entry := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Duration)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Size)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Flags)
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
pio.PutU32BE(b[n:], entry.Cts)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}, func() {
|
||||
for i := range self.Entries {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}, func() {
|
||||
for i := 0; i < int(_len_Entries); i++ {
|
||||
var flags uint32
|
||||
if i > 0 {
|
||||
flags = self.Flags
|
||||
} else {
|
||||
flags = self.FirstSampleFlags
|
||||
}
|
||||
entry := &self.Entries[i]
|
||||
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||
entry.Duration = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||
entry.Size = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||
entry.Flags = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||
entry.Cts = pio.U32BE(b[n:])
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func TrackFragRunEntry() {
|
||||
uint32(Duration)
|
||||
uint32(Size)
|
||||
uint32(Flags)
|
||||
uint32(Cts)
|
||||
}
|
||||
|
||||
func tfhd_TrackFragHeader() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
|
||||
uint64(BaseDataOffset, _code(func() {
|
||||
if self.Flags&TFHD_BASE_DATA_OFFSET != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(StsdId, _code(func() {
|
||||
if self.Flags&TFHD_STSD_ID != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultDuration, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_DURATION != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultSize, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_SIZE != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
|
||||
uint32(DefaultFlags, _code(func() {
|
||||
if self.Flags&TFHD_DEFAULT_FLAGS != 0 {
|
||||
doit()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func tfdt_TrackFragDecodeTime() {
|
||||
uint8(Version)
|
||||
uint24(Flags)
|
||||
time64(Time, _code(func() {
|
||||
if self.Version != 0 {
|
||||
PutTime64(b[n:], self.Time)
|
||||
n += 8
|
||||
} else {
|
||||
PutTime32(b[n:], self.Time)
|
||||
n += 4
|
||||
}
|
||||
}, func() {
|
||||
if self.Version != 0 {
|
||||
n += 8
|
||||
} else {
|
||||
n += 4
|
||||
}
|
||||
}, func() {
|
||||
if self.Version != 0 {
|
||||
self.Time = GetTime64(b[n:])
|
||||
n += 8
|
||||
} else {
|
||||
self.Time = GetTime32(b[n:])
|
||||
n += 4
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
113
format/mp4f/mp4fio/mp4io.go
Normal file
113
format/mp4f/mp4fio/mp4io.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package mp4fio
|
||||
|
||||
import (
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type ElemStreamDesc struct {
|
||||
DecConfig []byte
|
||||
TrackId uint16
|
||||
mp4io.AtomPos
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Children() []mp4io.Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) {
|
||||
b[n] = uint8(length & 0x7f)
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDescHdr() (n int) {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) {
|
||||
b[n] = tag
|
||||
n++
|
||||
n += self.fillLength(b[n:], datalen)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenESDescHdr() (n int) {
|
||||
return self.lenDescHdr() + 3
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], mp4io.MP4ESDescrTag, datalen)
|
||||
pio.PutU16BE(b[n:], self.TrackId)
|
||||
n += 2
|
||||
b[n] = 0 // flags
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) {
|
||||
return self.lenDescHdr() + 2 + 3 + 4 + 4 + self.lenDescHdr()
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], mp4io.MP4DecConfigDescrTag, datalen)
|
||||
b[n] = 0x40 // objectid
|
||||
n++
|
||||
b[n] = 0x15 // streamtype
|
||||
n++
|
||||
// buffer size db
|
||||
pio.PutU24BE(b[n:], 0)
|
||||
n += 3
|
||||
// max bitrage
|
||||
pio.PutU32BE(b[n:], uint32(200000))
|
||||
n += 4
|
||||
// avg bitrage
|
||||
pio.PutU32BE(b[n:], uint32(0))
|
||||
n += 4
|
||||
n += self.fillDescHdr(b[n:], mp4io.MP4DecSpecificDescrTag, datalen-n)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Len() (n int) {
|
||||
// len + tag
|
||||
return 8 +
|
||||
// ver + flags
|
||||
4 +
|
||||
self.lenESDescHdr() +
|
||||
self.lenDecConfigDescHdr() +
|
||||
len(self.DecConfig) +
|
||||
self.lenDescHdr() + 1
|
||||
}
|
||||
|
||||
// Version(4)
|
||||
// ESDesc(
|
||||
// MP4ESDescrTag
|
||||
// ESID(2)
|
||||
// ESFlags(1)
|
||||
// DecConfigDesc(
|
||||
// MP4DecConfigDescrTag
|
||||
// objectId streamType bufSize avgBitrate
|
||||
// DecSpecificDesc(
|
||||
// MP4DecSpecificDescrTag
|
||||
// decConfig
|
||||
// )
|
||||
// )
|
||||
// ?Desc(lenDescHdr+1)
|
||||
// )
|
||||
|
||||
func (self ElemStreamDesc) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(mp4io.ESDS))
|
||||
n += 8
|
||||
pio.PutU32BE(b[n:], 0) // Version
|
||||
n += 4
|
||||
datalen := self.Len()
|
||||
n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()+3)
|
||||
n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-3)
|
||||
copy(b[n:], self.DecConfig)
|
||||
n += len(self.DecConfig)
|
||||
n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr())
|
||||
b[n] = 0x02
|
||||
n++
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
402
format/mp4f/muxer.go
Normal file
402
format/mp4f/muxer.go
Normal file
@@ -0,0 +1,402 @@
|
||||
package mp4f
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
"github.com/deepch/vdk/format/mp4f/mp4fio"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type Muxer struct {
|
||||
maxFrames int
|
||||
bufw *bufio.Writer
|
||||
wpos int64
|
||||
fragmentIndex int
|
||||
streams []*Stream
|
||||
path string
|
||||
}
|
||||
|
||||
func NewMuxer(w *os.File) *Muxer {
|
||||
return &Muxer{}
|
||||
}
|
||||
func (self *Muxer) SetPath(path string) {
|
||||
self.path = path
|
||||
}
|
||||
func (self *Muxer) SetMaxFrames(count int) {
|
||||
self.maxFrames = count
|
||||
}
|
||||
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||
switch codec.Type() {
|
||||
case av.H264, av.AAC:
|
||||
default:
|
||||
err = fmt.Errorf("fmp4: codec type=%v is not supported", codec.Type())
|
||||
return
|
||||
}
|
||||
stream := &Stream{CodecData: codec}
|
||||
|
||||
stream.sample = &mp4io.SampleTable{
|
||||
SampleDesc: &mp4io.SampleDesc{},
|
||||
TimeToSample: &mp4io.TimeToSample{},
|
||||
SampleToChunk: &mp4io.SampleToChunk{},
|
||||
SampleSize: &mp4io.SampleSize{},
|
||||
ChunkOffset: &mp4io.ChunkOffset{},
|
||||
}
|
||||
|
||||
stream.trackAtom = &mp4io.Track{
|
||||
Header: &mp4io.TrackHeader{
|
||||
TrackId: int32(len(self.streams) + 1),
|
||||
Flags: 0x0007,
|
||||
Duration: 0,
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
},
|
||||
Media: &mp4io.Media{
|
||||
Header: &mp4io.MediaHeader{
|
||||
TimeScale: 1000,
|
||||
Duration: 0,
|
||||
Language: 21956,
|
||||
},
|
||||
Info: &mp4io.MediaInfo{
|
||||
Sample: stream.sample,
|
||||
Data: &mp4io.DataInfo{
|
||||
Refer: &mp4io.DataRefer{
|
||||
Url: &mp4io.DataReferUrl{
|
||||
Flags: 0x000001,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
switch codec.Type() {
|
||||
case av.H264:
|
||||
stream.sample.SyncSample = &mp4io.SyncSample{}
|
||||
stream.timeScale = 90000
|
||||
case av.AAC:
|
||||
stream.timeScale = 8000
|
||||
}
|
||||
|
||||
stream.muxer = self
|
||||
self.streams = append(self.streams, stream)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) buildEsds(conf []byte) *FDummy {
|
||||
esds := &mp4fio.ElemStreamDesc{DecConfig: conf}
|
||||
|
||||
b := make([]byte, esds.Len())
|
||||
esds.Marshal(b)
|
||||
|
||||
esdsDummy := FDummy{
|
||||
Data: b,
|
||||
Tag_: mp4io.Tag(uint32(mp4io.ESDS)),
|
||||
}
|
||||
return &esdsDummy
|
||||
}
|
||||
|
||||
func (self *Stream) buildHdlr() *FDummy {
|
||||
hdlr := FDummy{
|
||||
Data: []byte{
|
||||
0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69,
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x47, 0x6F, 0x6F, 0x64, 0x67, 0x61,
|
||||
0x6D, 0x65, 0x20, 0x47, 0x4F, 0x20, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x00, 0x00, 0x00},
|
||||
|
||||
Tag_: mp4io.Tag(uint32(mp4io.HDLR)),
|
||||
}
|
||||
return &hdlr
|
||||
}
|
||||
|
||||
func (self *Stream) buildAudioHdlr() *FDummy {
|
||||
hdlr := FDummy{
|
||||
Data: []byte{
|
||||
0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x6F,
|
||||
0x75, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x42, 0x65, 0x6E, 0x74, 0x6F, 0x34,
|
||||
0x20, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x48, 0x61, 0x6E,
|
||||
0x64, 0x6C, 0x65, 0x72, 0x00},
|
||||
|
||||
Tag_: mp4io.Tag(uint32(mp4io.HDLR)),
|
||||
}
|
||||
return &hdlr
|
||||
}
|
||||
|
||||
func (self *Stream) buildEdts() *FDummy {
|
||||
edts := FDummy{
|
||||
Data: []byte{
|
||||
0x00, 0x00, 0x00, 0x30, 0x65, 0x64, 0x74, 0x73,
|
||||
0x00, 0x00, 0x00, 0x28, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x9B, 0x24, 0x00, 0x00, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00,
|
||||
},
|
||||
Tag_: mp4io.Tag(0x65647473),
|
||||
}
|
||||
return &edts
|
||||
}
|
||||
|
||||
func (self *Stream) fillTrackAtom() (err error) {
|
||||
self.trackAtom.Media.Header.TimeScale = int32(self.timeScale)
|
||||
self.trackAtom.Media.Header.Duration = int32(self.duration)
|
||||
|
||||
if self.Type() == av.H264 {
|
||||
codec := self.CodecData.(h264parser.CodecData)
|
||||
width, height := codec.Width(), codec.Height()
|
||||
self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{
|
||||
DataRefIdx: 1,
|
||||
HorizontalResolution: 72,
|
||||
VorizontalResolution: 72,
|
||||
Width: int16(width),
|
||||
Height: int16(height),
|
||||
FrameCount: 1,
|
||||
Depth: 24,
|
||||
ColorTableId: -1,
|
||||
Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()},
|
||||
}
|
||||
self.trackAtom.Header.TrackWidth = float64(width)
|
||||
self.trackAtom.Header.TrackHeight = float64(height)
|
||||
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'v', 'i', 'd', 'e'},
|
||||
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
||||
}
|
||||
self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{
|
||||
Flags: 0x000001,
|
||||
}
|
||||
self.codecString = fmt.Sprintf("avc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication)
|
||||
} else if self.Type() == av.AAC {
|
||||
codec := self.CodecData.(aacparser.CodecData)
|
||||
self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{
|
||||
DataRefIdx: 1,
|
||||
NumberOfChannels: int16(codec.ChannelLayout().Count()),
|
||||
SampleSize: 16,
|
||||
SampleRate: float64(codec.SampleRate()),
|
||||
Unknowns: []mp4io.Atom{self.buildEsds(codec.MPEG4AudioConfigBytes())},
|
||||
}
|
||||
self.trackAtom.Header.Volume = 1
|
||||
self.trackAtom.Header.AlternateGroup = 1
|
||||
self.trackAtom.Header.Duration = 0
|
||||
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'s', 'o', 'u', 'n'},
|
||||
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
||||
}
|
||||
|
||||
self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}
|
||||
self.codecString = "mp4a.40.2"
|
||||
|
||||
} else {
|
||||
err = fmt.Errorf("fmp4: codec type=%d invalid", self.Type())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
element.streams = []*Stream{}
|
||||
for _, stream := range streams {
|
||||
if err = element.newStream(stream); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Muxer) GetInit(streams []av.CodecData) (string, []byte) {
|
||||
moov := &mp4io.Movie{
|
||||
Header: &mp4io.MovieHeader{
|
||||
PreferredRate: 1,
|
||||
PreferredVolume: 1,
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
NextTrackId: 3,
|
||||
Duration: 0,
|
||||
TimeScale: 1000,
|
||||
CreateTime: time0(),
|
||||
ModifyTime: time0(),
|
||||
PreviewTime: time0(),
|
||||
PreviewDuration: time0(),
|
||||
PosterTime: time0(),
|
||||
SelectionTime: time0(),
|
||||
SelectionDuration: time0(),
|
||||
CurrentTime: time0(),
|
||||
},
|
||||
Unknowns: []mp4io.Atom{element.buildMvex()},
|
||||
}
|
||||
var meta string
|
||||
for _, stream := range element.streams {
|
||||
if err := stream.fillTrackAtom(); err != nil {
|
||||
return meta, []byte{}
|
||||
}
|
||||
moov.Tracks = append(moov.Tracks, stream.trackAtom)
|
||||
meta += stream.codecString + ","
|
||||
}
|
||||
meta = meta[:len(meta)-1]
|
||||
ftypeData := []byte{0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x36, 0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x36, 0x64, 0x61, 0x73, 0x68}
|
||||
file := make([]byte, moov.Len()+len(ftypeData))
|
||||
copy(file, ftypeData)
|
||||
moov.Marshal(file[len(ftypeData):])
|
||||
return meta, file
|
||||
}
|
||||
|
||||
func (element *Muxer) WritePacket(pkt av.Packet, GOP bool) (bool, []byte, error) {
|
||||
stream := element.streams[pkt.Idx]
|
||||
if GOP {
|
||||
ts := time.Duration(0)
|
||||
if stream.lastpkt != nil {
|
||||
ts = pkt.Time - stream.lastpkt.Time
|
||||
}
|
||||
got, buf, err := stream.writePacketV3(pkt, ts, 5)
|
||||
stream.lastpkt = &pkt
|
||||
if err != nil {
|
||||
return false, []byte{}, err
|
||||
}
|
||||
return got, buf, err
|
||||
}
|
||||
ts := time.Duration(0)
|
||||
if stream.lastpkt != nil {
|
||||
ts = pkt.Time - stream.lastpkt.Time
|
||||
}
|
||||
got, buf, err := stream.writePacketV2(pkt, ts, 5)
|
||||
stream.lastpkt = &pkt
|
||||
if err != nil {
|
||||
return false, []byte{}, err
|
||||
}
|
||||
return got, buf, err
|
||||
}
|
||||
|
||||
func (element *Stream) writePacketV3(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
||||
trackID := pkt.Idx + 1
|
||||
var out []byte
|
||||
var got bool
|
||||
if element.sampleIndex > maxFrames && pkt.IsKeyFrame {
|
||||
element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8)
|
||||
out = make([]byte, element.moof.Len()+len(element.buffer))
|
||||
element.moof.Marshal(out)
|
||||
pio.PutU32BE(element.buffer, uint32(len(element.buffer)))
|
||||
copy(out[element.moof.Len():], element.buffer)
|
||||
element.sampleIndex = 0
|
||||
element.muxer.fragmentIndex++
|
||||
got = true
|
||||
}
|
||||
if element.sampleIndex == 0 {
|
||||
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
||||
element.moof.Tracks = []*mp4fio.TrackFrag{
|
||||
&mp4fio.TrackFrag{
|
||||
Header: &mp4fio.TrackFragHeader{
|
||||
Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00},
|
||||
},
|
||||
DecodeTime: &mp4fio.TrackFragDecodeTime{
|
||||
Version: 1,
|
||||
Flags: 0,
|
||||
Time: uint64(element.dts),
|
||||
},
|
||||
Run: &mp4fio.TrackFragRun{
|
||||
Flags: 0x000b05,
|
||||
FirstSampleFlags: 0x02000000,
|
||||
DataOffset: 0,
|
||||
Entries: []mp4io.TrackFragRunEntry{},
|
||||
},
|
||||
},
|
||||
}
|
||||
element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74}
|
||||
}
|
||||
runEnrty := mp4io.TrackFragRunEntry{
|
||||
Duration: uint32(element.timeToTs(rawdur)),
|
||||
Size: uint32(len(pkt.Data)),
|
||||
Cts: uint32(element.timeToTs(pkt.CompositionTime)),
|
||||
}
|
||||
element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty)
|
||||
element.buffer = append(element.buffer, pkt.Data...)
|
||||
element.sampleIndex++
|
||||
element.dts += element.timeToTs(rawdur)
|
||||
return got, out, nil
|
||||
}
|
||||
|
||||
func (element *Stream) writePacketV2(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
||||
trackID := pkt.Idx + 1
|
||||
if element.sampleIndex == 0 {
|
||||
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
||||
element.moof.Tracks = []*mp4fio.TrackFrag{
|
||||
&mp4fio.TrackFrag{
|
||||
Header: &mp4fio.TrackFragHeader{
|
||||
Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00},
|
||||
},
|
||||
DecodeTime: &mp4fio.TrackFragDecodeTime{
|
||||
Version: 1,
|
||||
Flags: 0,
|
||||
Time: uint64(element.dts),
|
||||
},
|
||||
Run: &mp4fio.TrackFragRun{
|
||||
Flags: 0x000b05,
|
||||
FirstSampleFlags: 0x02000000,
|
||||
DataOffset: 0,
|
||||
Entries: []mp4io.TrackFragRunEntry{},
|
||||
},
|
||||
},
|
||||
}
|
||||
element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74}
|
||||
}
|
||||
runEnrty := mp4io.TrackFragRunEntry{
|
||||
Duration: uint32(element.timeToTs(rawdur)),
|
||||
Size: uint32(len(pkt.Data)),
|
||||
Cts: uint32(element.timeToTs(pkt.CompositionTime)),
|
||||
}
|
||||
element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty)
|
||||
element.buffer = append(element.buffer, pkt.Data...)
|
||||
element.sampleIndex++
|
||||
element.dts += element.timeToTs(rawdur)
|
||||
if element.sampleIndex > maxFrames { // Количество фреймов в пакете
|
||||
element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8)
|
||||
file := make([]byte, element.moof.Len()+len(element.buffer))
|
||||
element.moof.Marshal(file)
|
||||
pio.PutU32BE(element.buffer, uint32(len(element.buffer)))
|
||||
copy(file[element.moof.Len():], element.buffer)
|
||||
element.sampleIndex = 0
|
||||
element.muxer.fragmentIndex++
|
||||
return true, file, nil
|
||||
}
|
||||
return false, []byte{}, nil
|
||||
}
|
||||
|
||||
func (self *Muxer) buildMvex() *FDummy {
|
||||
mvex := &FDummy{
|
||||
Data: []byte{
|
||||
0x00, 0x00, 0x00, 0x38, 0x6D, 0x76, 0x65, 0x78,
|
||||
0x00, 0x00, 0x00, 0x10, 0x6D, 0x65, 0x68, 0x64, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
Tag_: mp4io.Tag(0x6D766578),
|
||||
}
|
||||
for i := 1; i <= len(self.streams); i++ {
|
||||
trex := self.buildTrex(i)
|
||||
mvex.Data = append(mvex.Data, trex...)
|
||||
}
|
||||
|
||||
pio.PutU32BE(mvex.Data, uint32(len(mvex.Data)))
|
||||
return mvex
|
||||
}
|
||||
|
||||
func (self *Muxer) buildTrex(trackId int) []byte {
|
||||
return []byte{
|
||||
0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, uint8(trackId), 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00}
|
||||
}
|
||||
|
||||
func time0() time.Time {
|
||||
return time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
54
format/mp4f/stream.go
Normal file
54
format/mp4f/stream.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package mp4f
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/format/mp4"
|
||||
"github.com/deepch/vdk/format/mp4/mp4io"
|
||||
"github.com/deepch/vdk/format/mp4f/mp4fio"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
codecString string
|
||||
trackAtom *mp4io.Track
|
||||
idx int
|
||||
lastpkt *av.Packet
|
||||
timeScale int64
|
||||
duration int64
|
||||
muxer *Muxer
|
||||
demuxer *mp4.Demuxer
|
||||
sample *mp4io.SampleTable
|
||||
sampleIndex int
|
||||
sampleOffsetInChunk int64
|
||||
syncSampleIndex int
|
||||
dts int64
|
||||
sttsEntryIndex int
|
||||
sampleIndexInSttsEntry int
|
||||
cttsEntryIndex int
|
||||
sampleIndexInCttsEntry int
|
||||
chunkGroupIndex int
|
||||
chunkIndex int
|
||||
sampleIndexInChunk int
|
||||
sttsEntry *mp4io.TimeToSampleEntry
|
||||
cttsEntry *mp4io.CompositionOffsetEntry
|
||||
moof mp4fio.MovieFrag
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func timeToTs(tm time.Duration, timeScale int64) int64 {
|
||||
return int64(tm * time.Duration(timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func tsToTime(ts int64, timeScale int64) time.Duration {
|
||||
return time.Duration(ts) * time.Second / time.Duration(timeScale)
|
||||
}
|
||||
|
||||
func (obj *Stream) timeToTs(tm time.Duration) int64 {
|
||||
return int64(tm * time.Duration(obj.timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func (obj *Stream) tsToTime(ts int64) time.Duration {
|
||||
return time.Duration(ts) * time.Second / time.Duration(obj.timeScale)
|
||||
}
|
||||
1751
format/rtmp/rtmp.go
Normal file
1751
format/rtmp/rtmp.go
Normal file
File diff suppressed because it is too large
Load Diff
1241
format/rtsp/client.go
Normal file
1241
format/rtsp/client.go
Normal file
File diff suppressed because it is too large
Load Diff
25
format/rtsp/conn.go
Normal file
25
format/rtsp/conn.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connWithTimeout struct {
|
||||
Timeout time.Duration
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (self connWithTimeout) Read(p []byte) (n int, err error) {
|
||||
if self.Timeout > 0 {
|
||||
self.Conn.SetReadDeadline(time.Now().Add(self.Timeout))
|
||||
}
|
||||
return self.Conn.Read(p)
|
||||
}
|
||||
|
||||
func (self connWithTimeout) Write(p []byte) (n int, err error) {
|
||||
if self.Timeout > 0 {
|
||||
self.Conn.SetWriteDeadline(time.Now().Add(self.Timeout))
|
||||
}
|
||||
return self.Conn.Write(p)
|
||||
}
|
||||
117
format/rtsp/sdp/parser.go
Normal file
117
format/rtsp/sdp/parser.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
Uri string
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
AVType string
|
||||
Type av.CodecType
|
||||
TimeScale int
|
||||
Control string
|
||||
Rtpmap int
|
||||
Config []byte
|
||||
SpropParameterSets [][]byte
|
||||
PayloadType int
|
||||
SizeLength int
|
||||
IndexLength int
|
||||
}
|
||||
|
||||
func Parse(content string) (sess Session, medias []Media) {
|
||||
var media *Media
|
||||
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
typeval := strings.SplitN(line, "=", 2)
|
||||
if len(typeval) == 2 {
|
||||
fields := strings.SplitN(typeval[1], " ", 2)
|
||||
|
||||
switch typeval[0] {
|
||||
case "m":
|
||||
if len(fields) > 0 {
|
||||
switch fields[0] {
|
||||
case "audio", "video":
|
||||
medias = append(medias, Media{AVType: fields[0]})
|
||||
media = &medias[len(medias)-1]
|
||||
mfields := strings.Split(fields[1], " ")
|
||||
if len(mfields) >= 3 {
|
||||
media.PayloadType, _ = strconv.Atoi(mfields[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "u":
|
||||
sess.Uri = typeval[1]
|
||||
|
||||
case "a":
|
||||
if media != nil {
|
||||
for _, field := range fields {
|
||||
keyval := strings.SplitN(field, ":", 2)
|
||||
if len(keyval) >= 2 {
|
||||
key := keyval[0]
|
||||
val := keyval[1]
|
||||
switch key {
|
||||
case "control":
|
||||
media.Control = val
|
||||
case "rtpmap":
|
||||
media.Rtpmap, _ = strconv.Atoi(val)
|
||||
}
|
||||
}
|
||||
keyval = strings.Split(field, "/")
|
||||
if len(keyval) >= 2 {
|
||||
key := keyval[0]
|
||||
switch strings.ToUpper(key) {
|
||||
case "MPEG4-GENERIC":
|
||||
media.Type = av.AAC
|
||||
case "H264":
|
||||
media.Type = av.H264
|
||||
}
|
||||
if i, err := strconv.Atoi(keyval[1]); err == nil {
|
||||
media.TimeScale = i
|
||||
}
|
||||
if false {
|
||||
fmt.Println("sdp:", keyval[1], media.TimeScale)
|
||||
}
|
||||
}
|
||||
keyval = strings.Split(field, ";")
|
||||
if len(keyval) > 1 {
|
||||
for _, field := range keyval {
|
||||
keyval := strings.SplitN(field, "=", 2)
|
||||
if len(keyval) == 2 {
|
||||
key := strings.TrimSpace(keyval[0])
|
||||
val := keyval[1]
|
||||
switch key {
|
||||
case "config":
|
||||
media.Config, _ = hex.DecodeString(val)
|
||||
case "sizelength":
|
||||
media.SizeLength, _ = strconv.Atoi(val)
|
||||
case "indexlength":
|
||||
media.IndexLength, _ = strconv.Atoi(val)
|
||||
case "sprop-parameter-sets":
|
||||
fields := strings.Split(val, ",")
|
||||
for _, field := range fields {
|
||||
val, _ := base64.StdEncoding.DecodeString(field)
|
||||
media.SpropParameterSets = append(media.SpropParameterSets, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
44
format/rtsp/sdp/parser_test.go
Normal file
44
format/rtsp/sdp/parser_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
infos := Decode(`
|
||||
v=0
|
||||
o=- 1459325504777324 1 IN IP4 192.168.0.123
|
||||
s=RTSP/RTP stream from Network Video Server
|
||||
i=mpeg4cif
|
||||
t=0 0
|
||||
a=tool:LIVE555 Streaming Media v2009.09.28
|
||||
a=type:broadcast
|
||||
a=control:*
|
||||
a=range:npt=0-
|
||||
a=x-qt-text-nam:RTSP/RTP stream from Network Video Server
|
||||
a=x-qt-text-inf:mpeg4cif
|
||||
m=video 0 RTP/AVP 96
|
||||
c=IN IP4 0.0.0.0
|
||||
b=AS:300
|
||||
a=rtpmap:96 H264/90000
|
||||
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AHpWoKA9k,aO48gA==
|
||||
a=x-dimensions: 720, 480
|
||||
a=x-framerate: 15
|
||||
a=control:track1
|
||||
m=audio 0 RTP/AVP 96
|
||||
c=IN IP4 0.0.0.0
|
||||
b=AS:256
|
||||
a=rtpmap:96 MPEG4-GENERIC/16000/2
|
||||
a=fmtp:96 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408
|
||||
a=control:track2
|
||||
m=audio 0 RTP/AVP 0
|
||||
c=IN IP4 0.0.0.0
|
||||
b=AS:50
|
||||
a=recvonly
|
||||
a=control:rtsp://109.195.127.207:554/mpeg4cif/trackID=2
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;
|
||||
a=appversion:1.0
|
||||
`)
|
||||
t.Logf("%v", infos)
|
||||
}
|
||||
29
format/rtsp/stream.go
Normal file
29
format/rtsp/stream.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/format/rtsp/sdp"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
Sdp sdp.Media
|
||||
client *Client
|
||||
|
||||
// h264
|
||||
fuStarted bool
|
||||
fuBuffer []byte
|
||||
sps []byte
|
||||
pps []byte
|
||||
spsChanged bool
|
||||
ppsChanged bool
|
||||
|
||||
gotpkt bool
|
||||
pkt av.Packet
|
||||
timestamp uint32
|
||||
firsttimestamp uint32
|
||||
|
||||
lasttime time.Duration
|
||||
}
|
||||
291
format/ts/demuxer.go
Normal file
291
format/ts/demuxer.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/ts/tsio"
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
type Demuxer struct {
|
||||
r *bufio.Reader
|
||||
|
||||
pkts []av.Packet
|
||||
|
||||
pat *tsio.PAT
|
||||
pmt *tsio.PMT
|
||||
streams []*Stream
|
||||
tshdr []byte
|
||||
|
||||
stage int
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
tshdr: make([]byte, 188),
|
||||
r: bufio.NewReaderSize(r, pio.RecommendBufioSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
for _, stream := range self.streams {
|
||||
streams = append(streams, stream.CodecData)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) probe() (err error) {
|
||||
if self.stage == 0 {
|
||||
for {
|
||||
if self.pmt != nil {
|
||||
n := 0
|
||||
for _, stream := range self.streams {
|
||||
if stream.CodecData != nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == len(self.streams) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err = self.poll(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.stage++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for len(self.pkts) == 0 {
|
||||
if err = self.poll(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pkt = self.pkts[0]
|
||||
self.pkts = self.pkts[1:]
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) poll() (err error) {
|
||||
if err = self.readTSPacket(); err == io.EOF {
|
||||
var n int
|
||||
if n, err = self.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) initPMT(payload []byte) (err error) {
|
||||
var psihdrlen int
|
||||
var datalen int
|
||||
if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.pmt = &tsio.PMT{}
|
||||
if _, err = self.pmt.Unmarshal(payload[psihdrlen : psihdrlen+datalen]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
self.streams = []*Stream{}
|
||||
for i, info := range self.pmt.ElementaryStreamInfos {
|
||||
stream := &Stream{}
|
||||
stream.idx = i
|
||||
stream.demuxer = self
|
||||
stream.pid = info.ElementaryPID
|
||||
stream.streamType = info.StreamType
|
||||
switch info.StreamType {
|
||||
case tsio.ElementaryStreamTypeH264:
|
||||
self.streams = append(self.streams, stream)
|
||||
case tsio.ElementaryStreamTypeAdtsAAC:
|
||||
self.streams = append(self.streams, stream)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) payloadEnd() (n int, err error) {
|
||||
for _, stream := range self.streams {
|
||||
var i int
|
||||
if i, err = stream.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
n += i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) readTSPacket() (err error) {
|
||||
var hdrlen int
|
||||
var pid uint16
|
||||
var start bool
|
||||
var iskeyframe bool
|
||||
|
||||
if _, err = io.ReadFull(self.r, self.tshdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pid, start, iskeyframe, hdrlen, err = tsio.ParseTSHeader(self.tshdr); err != nil {
|
||||
return
|
||||
}
|
||||
payload := self.tshdr[hdrlen:]
|
||||
|
||||
if self.pat == nil {
|
||||
if pid == 0 {
|
||||
var psihdrlen int
|
||||
var datalen int
|
||||
if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.pat = &tsio.PAT{}
|
||||
if _, err = self.pat.Unmarshal(payload[psihdrlen : psihdrlen+datalen]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if self.pmt == nil {
|
||||
for _, entry := range self.pat.Entries {
|
||||
if entry.ProgramMapPID == pid {
|
||||
if err = self.initPMT(payload); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, stream := range self.streams {
|
||||
if pid == stream.pid {
|
||||
if err = stream.handleTSPacket(start, iskeyframe, payload); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) addPacket(payload []byte, timedelta time.Duration) {
|
||||
dts := self.dts
|
||||
pts := self.pts
|
||||
if dts == 0 {
|
||||
dts = pts
|
||||
}
|
||||
|
||||
demuxer := self.demuxer
|
||||
pkt := av.Packet{
|
||||
Idx: int8(self.idx),
|
||||
IsKeyFrame: self.iskeyframe,
|
||||
Time: dts + timedelta,
|
||||
Data: payload,
|
||||
}
|
||||
if pts != dts {
|
||||
pkt.CompositionTime = pts - dts
|
||||
}
|
||||
demuxer.pkts = append(demuxer.pkts, pkt)
|
||||
}
|
||||
|
||||
func (self *Stream) payloadEnd() (n int, err error) {
|
||||
payload := self.data
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
if self.datalen != 0 && len(payload) != self.datalen {
|
||||
err = fmt.Errorf("ts: packet size mismatch size=%d correct=%d", len(payload), self.datalen)
|
||||
return
|
||||
}
|
||||
self.data = nil
|
||||
|
||||
switch self.streamType {
|
||||
case tsio.ElementaryStreamTypeAdtsAAC:
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
|
||||
delta := time.Duration(0)
|
||||
for len(payload) > 0 {
|
||||
var hdrlen, framelen, samples int
|
||||
if config, hdrlen, framelen, samples, err = aacparser.ParseADTSHeader(payload); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CodecData == nil {
|
||||
if self.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfig(config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.addPacket(payload[hdrlen:framelen], delta)
|
||||
n++
|
||||
delta += time.Duration(samples) * time.Second / time.Duration(config.SampleRate)
|
||||
payload = payload[framelen:]
|
||||
}
|
||||
|
||||
case tsio.ElementaryStreamTypeH264:
|
||||
nalus, _ := h264parser.SplitNALUs(payload)
|
||||
var sps, pps []byte
|
||||
for _, nalu := range nalus {
|
||||
if len(nalu) > 0 {
|
||||
naltype := nalu[0] & 0x1f
|
||||
switch {
|
||||
case naltype == 7:
|
||||
sps = nalu
|
||||
case naltype == 8:
|
||||
pps = nalu
|
||||
case h264parser.IsDataNALU(nalu):
|
||||
// raw nalu to avcc
|
||||
b := make([]byte, 4+len(nalu))
|
||||
pio.PutU32BE(b[0:4], uint32(len(nalu)))
|
||||
copy(b[4:], nalu)
|
||||
self.addPacket(b, time.Duration(0))
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.CodecData == nil && len(sps) > 0 && len(pps) > 0 {
|
||||
if self.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) handleTSPacket(start bool, iskeyframe bool, payload []byte) (err error) {
|
||||
if start {
|
||||
if _, err = self.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
var hdrlen int
|
||||
if hdrlen, _, self.datalen, self.pts, self.dts, err = tsio.ParsePESHeader(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.iskeyframe = iskeyframe
|
||||
if self.datalen == 0 {
|
||||
self.data = make([]byte, 0, 4096)
|
||||
} else {
|
||||
self.data = make([]byte, 0, self.datalen)
|
||||
}
|
||||
self.data = append(self.data, payload[hdrlen:]...)
|
||||
} else {
|
||||
self.data = append(self.data, payload...)
|
||||
}
|
||||
return
|
||||
}
|
||||
26
format/ts/handler.go
Normal file
26
format/ts/handler.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/av/avutil"
|
||||
)
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".ts"
|
||||
|
||||
h.Probe = func(b []byte) bool {
|
||||
return b[0] == 0x47 && b[188] == 0x47
|
||||
}
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r)
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w)
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
||||
206
format/ts/muxer.go
Normal file
206
format/ts/muxer.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/ts/tsio"
|
||||
)
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC}
|
||||
|
||||
type Muxer struct {
|
||||
w io.Writer
|
||||
streams []*Stream
|
||||
PaddingToMakeCounterCont bool
|
||||
|
||||
psidata []byte
|
||||
peshdr []byte
|
||||
tshdr []byte
|
||||
adtshdr []byte
|
||||
datav [][]byte
|
||||
nalus [][]byte
|
||||
|
||||
tswpat, tswpmt *tsio.TSWriter
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return &Muxer{
|
||||
w: w,
|
||||
psidata: make([]byte, 188),
|
||||
peshdr: make([]byte, tsio.MaxPESHeaderLength),
|
||||
tshdr: make([]byte, tsio.MaxTSHeaderLength),
|
||||
adtshdr: make([]byte, aacparser.ADTSHeaderLength),
|
||||
nalus: make([][]byte, 16),
|
||||
datav: make([][]byte, 16),
|
||||
tswpmt: tsio.NewTSWriter(tsio.PMT_PID),
|
||||
tswpat: tsio.NewTSWriter(tsio.PAT_PID),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||
ok := false
|
||||
for _, c := range CodecTypes {
|
||||
if codec.Type() == c {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
err = fmt.Errorf("ts: codec type=%s is not supported", codec.Type())
|
||||
return
|
||||
}
|
||||
|
||||
pid := uint16(len(self.streams) + 0x100)
|
||||
stream := &Stream{
|
||||
muxer: self,
|
||||
CodecData: codec,
|
||||
pid: pid,
|
||||
tsw: tsio.NewTSWriter(pid),
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) writePaddingTSPackets(tsw *tsio.TSWriter) (err error) {
|
||||
for tsw.ContinuityCounter&0xf != 0x0 {
|
||||
if err = tsw.WritePackets(self.w, self.datav[:0], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
if self.PaddingToMakeCounterCont {
|
||||
for _, stream := range self.streams {
|
||||
if err = self.writePaddingTSPackets(stream.tsw); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) SetWriter(w io.Writer) {
|
||||
self.w = w
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePATPMT() (err error) {
|
||||
pat := tsio.PAT{
|
||||
Entries: []tsio.PATEntry{
|
||||
{ProgramNumber: 1, ProgramMapPID: tsio.PMT_PID},
|
||||
},
|
||||
}
|
||||
patlen := pat.Marshal(self.psidata[tsio.PSIHeaderLength:])
|
||||
n := tsio.FillPSI(self.psidata, tsio.TableIdPAT, tsio.TableExtPAT, patlen)
|
||||
self.datav[0] = self.psidata[:n]
|
||||
if err = self.tswpat.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var elemStreams []tsio.ElementaryStreamInfo
|
||||
for _, stream := range self.streams {
|
||||
switch stream.Type() {
|
||||
case av.AAC:
|
||||
elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{
|
||||
StreamType: tsio.ElementaryStreamTypeAdtsAAC,
|
||||
ElementaryPID: stream.pid,
|
||||
})
|
||||
case av.H264:
|
||||
elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{
|
||||
StreamType: tsio.ElementaryStreamTypeH264,
|
||||
ElementaryPID: stream.pid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pmt := tsio.PMT{
|
||||
PCRPID: 0x100,
|
||||
ElementaryStreamInfos: elemStreams,
|
||||
}
|
||||
pmtlen := pmt.Len()
|
||||
if pmtlen+tsio.PSIHeaderLength > len(self.psidata) {
|
||||
err = fmt.Errorf("ts: pmt too large")
|
||||
return
|
||||
}
|
||||
pmt.Marshal(self.psidata[tsio.PSIHeaderLength:])
|
||||
n = tsio.FillPSI(self.psidata, tsio.TableIdPMT, tsio.TableExtPMT, pmtlen)
|
||||
self.datav[0] = self.psidata[:n]
|
||||
if err = self.tswpmt.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
self.streams = []*Stream{}
|
||||
for _, stream := range streams {
|
||||
if err = self.newStream(stream); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = self.WritePATPMT(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
pkt.Time += time.Second
|
||||
|
||||
switch stream.Type() {
|
||||
case av.AAC:
|
||||
codec := stream.CodecData.(aacparser.CodecData)
|
||||
|
||||
n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdAAC, len(self.adtshdr)+len(pkt.Data), pkt.Time, 0)
|
||||
self.datav[0] = self.peshdr[:n]
|
||||
aacparser.FillADTSHeader(self.adtshdr, codec.Config, 1024, len(pkt.Data))
|
||||
self.datav[1] = self.adtshdr
|
||||
self.datav[2] = pkt.Data
|
||||
|
||||
if err = stream.tsw.WritePackets(self.w, self.datav[:3], pkt.Time, true, false); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case av.H264:
|
||||
codec := stream.CodecData.(h264parser.CodecData)
|
||||
|
||||
nalus := self.nalus[:0]
|
||||
if pkt.IsKeyFrame {
|
||||
nalus = append(nalus, codec.SPS())
|
||||
nalus = append(nalus, codec.PPS())
|
||||
}
|
||||
pktnalus, _ := h264parser.SplitNALUs(pkt.Data)
|
||||
for _, nalu := range pktnalus {
|
||||
nalus = append(nalus, nalu)
|
||||
}
|
||||
|
||||
datav := self.datav[:1]
|
||||
for i, nalu := range nalus {
|
||||
if i == 0 {
|
||||
datav = append(datav, h264parser.AUDBytes)
|
||||
} else {
|
||||
datav = append(datav, h264parser.StartCodeBytes)
|
||||
}
|
||||
datav = append(datav, nalu)
|
||||
}
|
||||
|
||||
n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdH264, -1, pkt.Time+pkt.CompositionTime, pkt.Time)
|
||||
datav[0] = self.peshdr[:n]
|
||||
|
||||
if err = stream.tsw.WritePackets(self.w, datav, pkt.Time, pkt.IsKeyFrame, false); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
27
format/ts/stream.go
Normal file
27
format/ts/stream.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/format/ts/tsio"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
|
||||
demuxer *Demuxer
|
||||
muxer *Muxer
|
||||
|
||||
pid uint16
|
||||
streamId uint8
|
||||
streamType uint8
|
||||
|
||||
tsw *tsio.TSWriter
|
||||
idx int
|
||||
|
||||
iskeyframe bool
|
||||
pts, dts time.Duration
|
||||
data []byte
|
||||
datalen int
|
||||
}
|
||||
55
format/ts/tsio/checksum.go
Normal file
55
format/ts/tsio/checksum.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package tsio
|
||||
|
||||
var ieeeCrc32Tbl = []uint32{
|
||||
0x00000000, 0xB71DC104, 0x6E3B8209, 0xD926430D, 0xDC760413, 0x6B6BC517,
|
||||
0xB24D861A, 0x0550471E, 0xB8ED0826, 0x0FF0C922, 0xD6D68A2F, 0x61CB4B2B,
|
||||
0x649B0C35, 0xD386CD31, 0x0AA08E3C, 0xBDBD4F38, 0x70DB114C, 0xC7C6D048,
|
||||
0x1EE09345, 0xA9FD5241, 0xACAD155F, 0x1BB0D45B, 0xC2969756, 0x758B5652,
|
||||
0xC836196A, 0x7F2BD86E, 0xA60D9B63, 0x11105A67, 0x14401D79, 0xA35DDC7D,
|
||||
0x7A7B9F70, 0xCD665E74, 0xE0B62398, 0x57ABE29C, 0x8E8DA191, 0x39906095,
|
||||
0x3CC0278B, 0x8BDDE68F, 0x52FBA582, 0xE5E66486, 0x585B2BBE, 0xEF46EABA,
|
||||
0x3660A9B7, 0x817D68B3, 0x842D2FAD, 0x3330EEA9, 0xEA16ADA4, 0x5D0B6CA0,
|
||||
0x906D32D4, 0x2770F3D0, 0xFE56B0DD, 0x494B71D9, 0x4C1B36C7, 0xFB06F7C3,
|
||||
0x2220B4CE, 0x953D75CA, 0x28803AF2, 0x9F9DFBF6, 0x46BBB8FB, 0xF1A679FF,
|
||||
0xF4F63EE1, 0x43EBFFE5, 0x9ACDBCE8, 0x2DD07DEC, 0x77708634, 0xC06D4730,
|
||||
0x194B043D, 0xAE56C539, 0xAB068227, 0x1C1B4323, 0xC53D002E, 0x7220C12A,
|
||||
0xCF9D8E12, 0x78804F16, 0xA1A60C1B, 0x16BBCD1F, 0x13EB8A01, 0xA4F64B05,
|
||||
0x7DD00808, 0xCACDC90C, 0x07AB9778, 0xB0B6567C, 0x69901571, 0xDE8DD475,
|
||||
0xDBDD936B, 0x6CC0526F, 0xB5E61162, 0x02FBD066, 0xBF469F5E, 0x085B5E5A,
|
||||
0xD17D1D57, 0x6660DC53, 0x63309B4D, 0xD42D5A49, 0x0D0B1944, 0xBA16D840,
|
||||
0x97C6A5AC, 0x20DB64A8, 0xF9FD27A5, 0x4EE0E6A1, 0x4BB0A1BF, 0xFCAD60BB,
|
||||
0x258B23B6, 0x9296E2B2, 0x2F2BAD8A, 0x98366C8E, 0x41102F83, 0xF60DEE87,
|
||||
0xF35DA999, 0x4440689D, 0x9D662B90, 0x2A7BEA94, 0xE71DB4E0, 0x500075E4,
|
||||
0x892636E9, 0x3E3BF7ED, 0x3B6BB0F3, 0x8C7671F7, 0x555032FA, 0xE24DF3FE,
|
||||
0x5FF0BCC6, 0xE8ED7DC2, 0x31CB3ECF, 0x86D6FFCB, 0x8386B8D5, 0x349B79D1,
|
||||
0xEDBD3ADC, 0x5AA0FBD8, 0xEEE00C69, 0x59FDCD6D, 0x80DB8E60, 0x37C64F64,
|
||||
0x3296087A, 0x858BC97E, 0x5CAD8A73, 0xEBB04B77, 0x560D044F, 0xE110C54B,
|
||||
0x38368646, 0x8F2B4742, 0x8A7B005C, 0x3D66C158, 0xE4408255, 0x535D4351,
|
||||
0x9E3B1D25, 0x2926DC21, 0xF0009F2C, 0x471D5E28, 0x424D1936, 0xF550D832,
|
||||
0x2C769B3F, 0x9B6B5A3B, 0x26D61503, 0x91CBD407, 0x48ED970A, 0xFFF0560E,
|
||||
0xFAA01110, 0x4DBDD014, 0x949B9319, 0x2386521D, 0x0E562FF1, 0xB94BEEF5,
|
||||
0x606DADF8, 0xD7706CFC, 0xD2202BE2, 0x653DEAE6, 0xBC1BA9EB, 0x0B0668EF,
|
||||
0xB6BB27D7, 0x01A6E6D3, 0xD880A5DE, 0x6F9D64DA, 0x6ACD23C4, 0xDDD0E2C0,
|
||||
0x04F6A1CD, 0xB3EB60C9, 0x7E8D3EBD, 0xC990FFB9, 0x10B6BCB4, 0xA7AB7DB0,
|
||||
0xA2FB3AAE, 0x15E6FBAA, 0xCCC0B8A7, 0x7BDD79A3, 0xC660369B, 0x717DF79F,
|
||||
0xA85BB492, 0x1F467596, 0x1A163288, 0xAD0BF38C, 0x742DB081, 0xC3307185,
|
||||
0x99908A5D, 0x2E8D4B59, 0xF7AB0854, 0x40B6C950, 0x45E68E4E, 0xF2FB4F4A,
|
||||
0x2BDD0C47, 0x9CC0CD43, 0x217D827B, 0x9660437F, 0x4F460072, 0xF85BC176,
|
||||
0xFD0B8668, 0x4A16476C, 0x93300461, 0x242DC565, 0xE94B9B11, 0x5E565A15,
|
||||
0x87701918, 0x306DD81C, 0x353D9F02, 0x82205E06, 0x5B061D0B, 0xEC1BDC0F,
|
||||
0x51A69337, 0xE6BB5233, 0x3F9D113E, 0x8880D03A, 0x8DD09724, 0x3ACD5620,
|
||||
0xE3EB152D, 0x54F6D429, 0x7926A9C5, 0xCE3B68C1, 0x171D2BCC, 0xA000EAC8,
|
||||
0xA550ADD6, 0x124D6CD2, 0xCB6B2FDF, 0x7C76EEDB, 0xC1CBA1E3, 0x76D660E7,
|
||||
0xAFF023EA, 0x18EDE2EE, 0x1DBDA5F0, 0xAAA064F4, 0x738627F9, 0xC49BE6FD,
|
||||
0x09FDB889, 0xBEE0798D, 0x67C63A80, 0xD0DBFB84, 0xD58BBC9A, 0x62967D9E,
|
||||
0xBBB03E93, 0x0CADFF97, 0xB110B0AF, 0x060D71AB, 0xDF2B32A6, 0x6836F3A2,
|
||||
0x6D66B4BC, 0xDA7B75B8, 0x035D36B5, 0xB440F7B1, 0x00000001,
|
||||
}
|
||||
|
||||
func calcCRC32(crc uint32, data []byte) uint32 {
|
||||
for _, b := range data {
|
||||
crc = ieeeCrc32Tbl[b^byte(crc)] ^ (crc >> 8)
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
589
format/ts/tsio/tsio.go
Normal file
589
format/ts/tsio/tsio.go
Normal file
@@ -0,0 +1,589 @@
|
||||
package tsio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/utils/bits/pio"
|
||||
)
|
||||
|
||||
const (
|
||||
StreamIdH264 = 0xe0
|
||||
StreamIdAAC = 0xc0
|
||||
)
|
||||
|
||||
const (
|
||||
PAT_PID = 0
|
||||
PMT_PID = 0x1000
|
||||
)
|
||||
|
||||
const TableIdPMT = 2
|
||||
const TableExtPMT = 1
|
||||
|
||||
const TableIdPAT = 0
|
||||
const TableExtPAT = 1
|
||||
|
||||
const MaxPESHeaderLength = 19
|
||||
const MaxTSHeaderLength = 12
|
||||
|
||||
var ErrPESHeader = fmt.Errorf("invalid PES header")
|
||||
var ErrPSIHeader = fmt.Errorf("invalid PSI header")
|
||||
var ErrParsePMT = fmt.Errorf("invalid PMT")
|
||||
var ErrParsePAT = fmt.Errorf("invalid PAT")
|
||||
|
||||
const (
|
||||
ElementaryStreamTypeH264 = 0x1B
|
||||
ElementaryStreamTypeAdtsAAC = 0x0F
|
||||
)
|
||||
|
||||
type PATEntry struct {
|
||||
ProgramNumber uint16
|
||||
NetworkPID uint16
|
||||
ProgramMapPID uint16
|
||||
}
|
||||
|
||||
type PAT struct {
|
||||
Entries []PATEntry
|
||||
}
|
||||
|
||||
func (self PAT) Len() (n int) {
|
||||
return len(self.Entries) * 4
|
||||
}
|
||||
|
||||
func (self PAT) Marshal(b []byte) (n int) {
|
||||
for _, entry := range self.Entries {
|
||||
pio.PutU16BE(b[n:], entry.ProgramNumber)
|
||||
n += 2
|
||||
if entry.ProgramNumber == 0 {
|
||||
pio.PutU16BE(b[n:], entry.NetworkPID&0x1fff|7<<13)
|
||||
n += 2
|
||||
} else {
|
||||
pio.PutU16BE(b[n:], entry.ProgramMapPID&0x1fff|7<<13)
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *PAT) Unmarshal(b []byte) (n int, err error) {
|
||||
for n < len(b) {
|
||||
if n+4 <= len(b) {
|
||||
var entry PATEntry
|
||||
entry.ProgramNumber = pio.U16BE(b[n:])
|
||||
n += 2
|
||||
if entry.ProgramNumber == 0 {
|
||||
entry.NetworkPID = pio.U16BE(b[n:]) & 0x1fff
|
||||
n += 2
|
||||
} else {
|
||||
entry.ProgramMapPID = pio.U16BE(b[n:]) & 0x1fff
|
||||
n += 2
|
||||
}
|
||||
self.Entries = append(self.Entries, entry)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if n < len(b) {
|
||||
err = ErrParsePAT
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Descriptor struct {
|
||||
Tag uint8
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ElementaryStreamInfo struct {
|
||||
StreamType uint8
|
||||
ElementaryPID uint16
|
||||
Descriptors []Descriptor
|
||||
}
|
||||
|
||||
type PMT struct {
|
||||
PCRPID uint16
|
||||
ProgramDescriptors []Descriptor
|
||||
ElementaryStreamInfos []ElementaryStreamInfo
|
||||
}
|
||||
|
||||
func (self PMT) Len() (n int) {
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
n += 2
|
||||
|
||||
// desclen(16)
|
||||
n += 2
|
||||
|
||||
for _, desc := range self.ProgramDescriptors {
|
||||
n += 2 + len(desc.Data)
|
||||
}
|
||||
|
||||
for _, info := range self.ElementaryStreamInfos {
|
||||
// streamType
|
||||
n += 1
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
n += 2
|
||||
|
||||
// Reserved(6)
|
||||
// ES Info length length(10)
|
||||
n += 2
|
||||
|
||||
for _, desc := range info.Descriptors {
|
||||
n += 2 + len(desc.Data)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) fillDescs(b []byte, descs []Descriptor) (n int) {
|
||||
for _, desc := range descs {
|
||||
b[n] = desc.Tag
|
||||
n++
|
||||
b[n] = uint8(len(desc.Data))
|
||||
n++
|
||||
copy(b[n:], desc.Data)
|
||||
n += len(desc.Data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) Marshal(b []byte) (n int) {
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
pio.PutU16BE(b[n:], self.PCRPID|7<<13)
|
||||
n += 2
|
||||
|
||||
hold := n
|
||||
n += 2
|
||||
pos := n
|
||||
n += self.fillDescs(b[n:], self.ProgramDescriptors)
|
||||
desclen := n - pos
|
||||
pio.PutU16BE(b[hold:], uint16(desclen)|0xf<<12)
|
||||
|
||||
for _, info := range self.ElementaryStreamInfos {
|
||||
b[n] = info.StreamType
|
||||
n++
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
pio.PutU16BE(b[n:], info.ElementaryPID|7<<13)
|
||||
n += 2
|
||||
|
||||
hold := n
|
||||
n += 2
|
||||
pos := n
|
||||
n += self.fillDescs(b[n:], info.Descriptors)
|
||||
desclen := n - pos
|
||||
pio.PutU16BE(b[hold:], uint16(desclen)|0x3c<<10)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) parseDescs(b []byte) (descs []Descriptor, err error) {
|
||||
n := 0
|
||||
for n < len(b) {
|
||||
if n+2 <= len(b) {
|
||||
desc := Descriptor{}
|
||||
desc.Tag = b[n]
|
||||
desc.Data = make([]byte, b[n+1])
|
||||
n += 2
|
||||
if n+len(desc.Data) < len(b) {
|
||||
copy(desc.Data, b[n:])
|
||||
descs = append(descs, desc)
|
||||
n += len(desc.Data)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if n < len(b) {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *PMT) Unmarshal(b []byte) (n int, err error) {
|
||||
if len(b) < n+4 {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
self.PCRPID = pio.U16BE(b[0:2]) & 0x1fff
|
||||
n += 2
|
||||
|
||||
// Reserved(4)=0xf
|
||||
// Reserved(2)=0x0
|
||||
// Program info length(10)
|
||||
desclen := int(pio.U16BE(b[2:4]) & 0x3ff)
|
||||
n += 2
|
||||
|
||||
if desclen > 0 {
|
||||
if len(b) < n+desclen {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
if self.ProgramDescriptors, err = self.parseDescs(b[n : n+desclen]); err != nil {
|
||||
return
|
||||
}
|
||||
n += desclen
|
||||
}
|
||||
|
||||
for n < len(b) {
|
||||
if len(b) < n+5 {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
|
||||
var info ElementaryStreamInfo
|
||||
info.StreamType = b[n]
|
||||
n++
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
info.ElementaryPID = pio.U16BE(b[n:]) & 0x1fff
|
||||
n += 2
|
||||
|
||||
// Reserved(6)
|
||||
// ES Info length(10)
|
||||
desclen := int(pio.U16BE(b[n:]) & 0x3ff)
|
||||
n += 2
|
||||
|
||||
if desclen > 0 {
|
||||
if len(b) < n+desclen {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
if info.Descriptors, err = self.parseDescs(b[n : n+desclen]); err != nil {
|
||||
return
|
||||
}
|
||||
n += desclen
|
||||
}
|
||||
|
||||
self.ElementaryStreamInfos = append(self.ElementaryStreamInfos, info)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParsePSI(h []byte) (tableid uint8, tableext uint16, hdrlen int, datalen int, err error) {
|
||||
if len(h) < 8 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// pointer(8)
|
||||
pointer := h[0]
|
||||
hdrlen++
|
||||
if pointer > 0 {
|
||||
hdrlen += int(pointer)
|
||||
if len(h) < hdrlen {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(h) < hdrlen+12 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// table_id(8)
|
||||
tableid = h[hdrlen]
|
||||
hdrlen++
|
||||
|
||||
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
||||
datalen = int(pio.U16BE(h[hdrlen:]))&0x3ff - 9
|
||||
hdrlen += 2
|
||||
|
||||
if datalen < 0 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// Table ID extension(16)
|
||||
tableext = pio.U16BE(h[hdrlen:])
|
||||
hdrlen += 2
|
||||
|
||||
// resverd(2)=3
|
||||
// version(5)
|
||||
// Current_next_indicator(1)
|
||||
hdrlen++
|
||||
|
||||
// section_number(8)
|
||||
hdrlen++
|
||||
|
||||
// last_section_number(8)
|
||||
hdrlen++
|
||||
|
||||
// data
|
||||
|
||||
// crc(32)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const PSIHeaderLength = 9
|
||||
|
||||
func FillPSI(h []byte, tableid uint8, tableext uint16, datalen int) (n int) {
|
||||
// pointer(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
// table_id(8)
|
||||
h[n] = tableid
|
||||
n++
|
||||
|
||||
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
||||
pio.PutU16BE(h[n:], uint16(0xa<<12|2+3+4+datalen))
|
||||
n += 2
|
||||
|
||||
// Table ID extension(16)
|
||||
pio.PutU16BE(h[n:], tableext)
|
||||
n += 2
|
||||
|
||||
// resverd(2)=3,version(5)=0,Current_next_indicator(1)=1
|
||||
h[n] = 0x3<<6 | 1
|
||||
n++
|
||||
|
||||
// section_number(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
// last_section_number(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
n += datalen
|
||||
|
||||
crc := calcCRC32(0xffffffff, h[1:n])
|
||||
pio.PutU32LE(h[n:], crc)
|
||||
n += 4
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TimeToPCR(tm time.Duration) (pcr uint64) {
|
||||
// base(33)+resverd(6)+ext(9)
|
||||
ts := uint64(tm * PCR_HZ / time.Second)
|
||||
base := ts / 300
|
||||
ext := ts % 300
|
||||
pcr = base<<15 | 0x3f<<9 | ext
|
||||
return
|
||||
}
|
||||
|
||||
func PCRToTime(pcr uint64) (tm time.Duration) {
|
||||
base := pcr >> 15
|
||||
ext := pcr & 0x1ff
|
||||
ts := base*300 + ext
|
||||
tm = time.Duration(ts) * time.Second / time.Duration(PCR_HZ)
|
||||
return
|
||||
}
|
||||
|
||||
func TimeToTs(tm time.Duration) (v uint64) {
|
||||
ts := uint64(tm * PTS_HZ / time.Second)
|
||||
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
||||
v = ((ts>>30)&0x7)<<33 | ((ts>>15)&0x7fff)<<17 | (ts&0x7fff)<<1 | 0x100010001
|
||||
return
|
||||
}
|
||||
|
||||
func TsToTime(v uint64) (tm time.Duration) {
|
||||
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
||||
ts := (((v >> 33) & 0x7) << 30) | (((v >> 17) & 0x7fff) << 15) | ((v >> 1) & 0x7fff)
|
||||
tm = time.Duration(ts) * time.Second / time.Duration(PTS_HZ)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
PTS_HZ = 90000
|
||||
PCR_HZ = 27000000
|
||||
)
|
||||
|
||||
func ParsePESHeader(h []byte) (hdrlen int, streamid uint8, datalen int, pts, dts time.Duration, err error) {
|
||||
if h[0] != 0 || h[1] != 0 || h[2] != 1 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
streamid = h[3]
|
||||
|
||||
flags := h[7]
|
||||
hdrlen = int(h[8]) + 9
|
||||
|
||||
datalen = int(pio.U16BE(h[4:6]))
|
||||
if datalen > 0 {
|
||||
datalen -= int(h[8]) + 3
|
||||
}
|
||||
|
||||
const PTS = 1 << 7
|
||||
const DTS = 1 << 6
|
||||
|
||||
if flags&PTS != 0 {
|
||||
if len(h) < 14 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
pts = TsToTime(pio.U40BE(h[9:14]))
|
||||
if flags&DTS != 0 {
|
||||
if len(h) < 19 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
dts = TsToTime(pio.U40BE(h[14:19]))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func FillPESHeader(h []byte, streamid uint8, datalen int, pts, dts time.Duration) (n int) {
|
||||
h[0] = 0
|
||||
h[1] = 0
|
||||
h[2] = 1
|
||||
h[3] = streamid
|
||||
|
||||
const PTS = 1 << 7
|
||||
const DTS = 1 << 6
|
||||
|
||||
var flags uint8
|
||||
if pts != 0 {
|
||||
flags |= PTS
|
||||
if dts != 0 {
|
||||
flags |= DTS
|
||||
}
|
||||
}
|
||||
|
||||
if flags&PTS != 0 {
|
||||
n += 5
|
||||
}
|
||||
if flags&DTS != 0 {
|
||||
n += 5
|
||||
}
|
||||
|
||||
// packet_length(16) if zero then variable length
|
||||
// Specifies the number of bytes remaining in the packet after this field. Can be zero.
|
||||
// If the PES packet length is set to zero, the PES packet can be of any length.
|
||||
// A value of zero for the PES packet length can be used only when the PES packet payload is a **video** elementary stream.
|
||||
var pktlen uint16
|
||||
if datalen >= 0 {
|
||||
pktlen = uint16(datalen + n + 3)
|
||||
}
|
||||
pio.PutU16BE(h[4:6], pktlen)
|
||||
|
||||
h[6] = 2<<6 | 1 // resverd(6,2)=2,original_or_copy(0,1)=1
|
||||
h[7] = flags
|
||||
h[8] = uint8(n)
|
||||
|
||||
// pts(40)?
|
||||
// dts(40)?
|
||||
if flags&PTS != 0 {
|
||||
if flags&DTS != 0 {
|
||||
pio.PutU40BE(h[9:14], TimeToTs(pts)|3<<36)
|
||||
pio.PutU40BE(h[14:19], TimeToTs(dts)|1<<36)
|
||||
} else {
|
||||
pio.PutU40BE(h[9:14], TimeToTs(pts)|2<<36)
|
||||
}
|
||||
}
|
||||
|
||||
n += 9
|
||||
return
|
||||
}
|
||||
|
||||
type TSWriter struct {
|
||||
w io.Writer
|
||||
ContinuityCounter uint
|
||||
tshdr []byte
|
||||
}
|
||||
|
||||
func NewTSWriter(pid uint16) *TSWriter {
|
||||
w := &TSWriter{}
|
||||
w.tshdr = make([]byte, 188)
|
||||
w.tshdr[0] = 0x47
|
||||
pio.PutU16BE(w.tshdr[1:3], pid&0x1fff)
|
||||
for i := 6; i < 188; i++ {
|
||||
w.tshdr[i] = 0xff
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (self *TSWriter) WritePackets(w io.Writer, datav [][]byte, pcr time.Duration, sync bool, paddata bool) (err error) {
|
||||
datavlen := pio.VecLen(datav)
|
||||
writev := make([][]byte, len(datav))
|
||||
writepos := 0
|
||||
|
||||
for writepos < datavlen {
|
||||
self.tshdr[1] = self.tshdr[1] & 0x1f
|
||||
self.tshdr[3] = byte(self.ContinuityCounter)&0xf | 0x30
|
||||
self.tshdr[5] = 0 // flags
|
||||
hdrlen := 6
|
||||
self.ContinuityCounter++
|
||||
|
||||
if writepos == 0 {
|
||||
self.tshdr[1] = 0x40 | self.tshdr[1] // Payload Unit Start Indicator
|
||||
if pcr != 0 {
|
||||
hdrlen += 6
|
||||
self.tshdr[5] = 0x10 | self.tshdr[5] // PCR flag (Discontinuity indicator 0x80)
|
||||
pio.PutU48BE(self.tshdr[6:12], TimeToPCR(pcr))
|
||||
}
|
||||
if sync {
|
||||
self.tshdr[5] = 0x40 | self.tshdr[5] // Random Access indicator
|
||||
}
|
||||
}
|
||||
|
||||
padtail := 0
|
||||
end := writepos + 188 - hdrlen
|
||||
if end > datavlen {
|
||||
if paddata {
|
||||
padtail = end - datavlen
|
||||
} else {
|
||||
hdrlen += end - datavlen
|
||||
}
|
||||
end = datavlen
|
||||
}
|
||||
n := pio.VecSliceTo(datav, writev, writepos, end)
|
||||
|
||||
self.tshdr[4] = byte(hdrlen) - 5 // length
|
||||
if _, err = w.Write(self.tshdr[:hdrlen]); err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if _, err = w.Write(writev[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if padtail > 0 {
|
||||
if _, err = w.Write(self.tshdr[188-padtail : 188]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writepos = end
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTSHeader(tshdr []byte) (pid uint16, start bool, iskeyframe bool, hdrlen int, err error) {
|
||||
// https://en.wikipedia.org/wiki/MPEG_transport_stream
|
||||
if tshdr[0] != 0x47 {
|
||||
err = fmt.Errorf("tshdr sync invalid")
|
||||
return
|
||||
}
|
||||
pid = uint16((tshdr[1]&0x1f))<<8 | uint16(tshdr[2])
|
||||
start = tshdr[1]&0x40 != 0
|
||||
hdrlen += 4
|
||||
if tshdr[3]&0x20 != 0 {
|
||||
hdrlen += int(tshdr[4]) + 1
|
||||
iskeyframe = tshdr[5]&0x40 != 0
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user