test mp4
This commit is contained in:
parent
4289f32dc8
commit
ff7f04332e
@ -1,14 +1,13 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
|
||||||
"go/printer"
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getexprs(e ast.Expr) string {
|
func getexprs(e ast.Expr) string {
|
||||||
@ -967,7 +966,7 @@ func genatoms(filename, outfilename string) {
|
|||||||
&ast.GenDecl{
|
&ast.GenDecl{
|
||||||
Tok: token.IMPORT,
|
Tok: token.IMPORT,
|
||||||
Specs: []ast.Spec{
|
Specs: []ast.Spec{
|
||||||
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/nareix/joy4/utils/bits/pio"`}},
|
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/deepch/vdk/utils/bits/pio"`}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&ast.GenDecl{
|
&ast.GenDecl{
|
||||||
@ -1054,4 +1053,3 @@ func main() {
|
|||||||
genatoms(os.Args[2], os.Args[3])
|
genatoms(os.Args[2], os.Args[3])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
|
||||||
"go/printer"
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getexprs(e ast.Expr) string {
|
func getexprs(e ast.Expr) string {
|
||||||
@ -967,7 +966,7 @@ func genatoms(filename, outfilename string) {
|
|||||||
&ast.GenDecl{
|
&ast.GenDecl{
|
||||||
Tok: token.IMPORT,
|
Tok: token.IMPORT,
|
||||||
Specs: []ast.Spec{
|
Specs: []ast.Spec{
|
||||||
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/nareix/joy4/utils/bits/pio"`}},
|
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/deepch/vdk/utils/bits/pio"`}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&ast.GenDecl{
|
&ast.GenDecl{
|
||||||
@ -1054,4 +1053,3 @@ func main() {
|
|||||||
genatoms(os.Args[2], os.Args[3])
|
genatoms(os.Args[2], os.Args[3])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
446
format/mp4m/demuxer.go
Normal file
446
format/mp4m/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/mp4m/handler.go
Normal file
32
format/mp4m/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
|
||||||
|
}
|
3822
format/mp4m/mp4io/atoms.go
Normal file
3822
format/mp4m/mp4io/atoms.go
Normal file
File diff suppressed because it is too large
Load Diff
1055
format/mp4m/mp4io/gen/gen.go
Normal file
1055
format/mp4m/mp4io/gen/gen.go
Normal file
File diff suppressed because it is too large
Load Diff
436
format/mp4m/mp4io/gen/pattern.go
Normal file
436
format/mp4m/mp4io/gen/pattern.go
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
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/mp4m/mp4io/mp4io.go
Normal file
502
format/mp4m/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/mp4m/muxer.go
Normal file
283
format/mp4m/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/mp4m/stream.go
Normal file
59
format/mp4m/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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user