add demuxer.go
This commit is contained in:
parent
b69852c281
commit
e060a3e79d
212
demuxer.go
Normal file
212
demuxer.go
Normal file
@ -0,0 +1,212 @@
|
||||
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"github.com/nareix/mp4/atom"
|
||||
_ "os"
|
||||
"fmt"
|
||||
"io"
|
||||
_ "log"
|
||||
)
|
||||
|
||||
type Demuxer struct {
|
||||
R io.ReadSeeker
|
||||
Tracks []*Track
|
||||
TrackH264 *Track
|
||||
TrackAAC *Track
|
||||
MovieAtom *atom.Movie
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadHeader() (err error) {
|
||||
var N int64
|
||||
var moov *atom.Movie
|
||||
|
||||
if N, err = self.R.Seek(0, 2); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.R.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lr := &io.LimitedReader{R: self.R, N: N}
|
||||
for lr.N > 0 {
|
||||
var ar *io.LimitedReader
|
||||
|
||||
var cc4 string
|
||||
if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cc4 == "moov" {
|
||||
if moov, err = atom.ReadMovie(ar); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if moov == nil {
|
||||
err = fmt.Errorf("'moov' atom not found")
|
||||
return
|
||||
}
|
||||
self.MovieAtom = moov
|
||||
|
||||
self.Tracks = []*Track{}
|
||||
for _, atrack := range(moov.Tracks) {
|
||||
track := &Track{
|
||||
TrackAtom: atrack,
|
||||
r: self.R,
|
||||
}
|
||||
if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil {
|
||||
track.sample = atrack.Media.Info.Sample
|
||||
} else {
|
||||
err = fmt.Errorf("sample table not found")
|
||||
return
|
||||
}
|
||||
if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil {
|
||||
track.Type = H264
|
||||
self.TrackH264 = track
|
||||
if len(record.PPS) > 0 {
|
||||
track.PPS = record.PPS[0]
|
||||
}
|
||||
if len(record.SPS) > 0 {
|
||||
track.SPS = record.SPS[0]
|
||||
}
|
||||
self.Tracks = append(self.Tracks, track)
|
||||
} else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil {
|
||||
self.TrackAAC = track
|
||||
track.Type = AAC
|
||||
self.Tracks = append(self.Tracks, track)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) setSampleIndex(index int) (err error) {
|
||||
found := false
|
||||
start := 0
|
||||
self.chunkGroupIndex = 0
|
||||
|
||||
for self.chunkIndex = range(self.sample.ChunkOffset.Entries) {
|
||||
n := self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk
|
||||
if index >= start && index < start+n {
|
||||
found = true
|
||||
self.sampleIndexInChunk = index-start
|
||||
break
|
||||
}
|
||||
start += n
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
self.sampleIndex = index
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) incSampleIndex() {
|
||||
self.sampleIndexInChunk++
|
||||
if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk {
|
||||
self.chunkIndex++
|
||||
self.sampleIndexInChunk = 0
|
||||
}
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
self.sampleIndex++
|
||||
}
|
||||
|
||||
func (self *Track) SampleCount() int {
|
||||
if self.sample.SampleSize.SampleSize == 0 {
|
||||
chunkGroupIndex := 0
|
||||
count := 0
|
||||
for chunkIndex := range(self.sample.ChunkOffset.Entries) {
|
||||
n := self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk
|
||||
count += n
|
||||
if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
chunkIndex+1 == self.sample.SampleToChunk.Entries[chunkGroupIndex+1].FirstChunk {
|
||||
chunkGroupIndex++
|
||||
}
|
||||
}
|
||||
return count
|
||||
} else {
|
||||
return len(self.sample.SampleSize.Entries)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) ReadSampleAtIndex(index int) (pts int64, dts int64, isKeyFrame bool, data []byte, err error) {
|
||||
if self.sampleIndex+1 == index {
|
||||
self.incSampleIndex()
|
||||
} else if self.sampleIndex != index {
|
||||
if err = self.setSampleIndex(index); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if self.chunkIndex > len(self.sample.ChunkOffset.Entries) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex]
|
||||
sampleOffset := 0
|
||||
sampleSize := 0
|
||||
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
sampleOffset = chunkOffset + self.sampleIndexInChunk*self.sample.SampleSize.SampleSize
|
||||
} else {
|
||||
sampleOffset = chunkOffset
|
||||
for i := self.sampleIndex-self.sampleIndexInChunk; i < self.sampleIndex; i++ {
|
||||
sampleOffset += self.sample.SampleSize.Entries[i]
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil {
|
||||
return
|
||||
}
|
||||
data = make([]byte, sampleSize)
|
||||
if _, err = self.r.Read(data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) Duration() float32 {
|
||||
total := int64(0)
|
||||
for _, entry := range(self.sample.TimeToSample.Entries) {
|
||||
total += int64(entry.Duration*entry.Count)
|
||||
}
|
||||
return float32(total)/float32(self.TrackAtom.Media.Header.TimeScale)
|
||||
}
|
||||
|
||||
func (self *Track) TimeToSampleIndex(second float32) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *Track) TimeStampToTime(ts int64) float32 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (self *Track) WriteSample(pts int64, dts int64, data []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
26
example/example.go
Normal file
26
example/example.go
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nareix/mp4"
|
||||
"os"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func DemuxExample() {
|
||||
file, _ := os.Open("test.mp4")
|
||||
|
||||
demuxer := &mp4.Demuxer{
|
||||
R: file,
|
||||
}
|
||||
demuxer.ReadHeader()
|
||||
|
||||
fmt.Println("Duration: ", demuxer.TrackH264.Duration())
|
||||
count := demuxer.TrackH264.SampleCount()
|
||||
fmt.Println("SampleCount: ", count)
|
||||
}
|
||||
|
||||
func main() {
|
||||
DemuxExample()
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
mp4 "./.."
|
||||
"flag"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
testconv := flag.Bool("testconv", false, "")
|
||||
probe := flag.Bool("probe", false, "")
|
||||
flag.Parse()
|
||||
|
||||
if *testconv {
|
||||
if err := mp4.TestConvert(flag.Arg(0)); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if *probe {
|
||||
if err := mp4.ProbeFile(flag.Arg(0)); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
200
test.go
200
test.go
@ -1,200 +0,0 @@
|
||||
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"github.com/nareix/mp4/atom"
|
||||
"os"
|
||||
"io"
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
/*
|
||||
func TestRewrite(filename string) (err error) {
|
||||
var infile *os.File
|
||||
if infile, err = os.Open(filename); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var outfile *os.File
|
||||
if outfile, err = os.Open(filename+".out.mp4"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
func getAVCDecoderConfRecordByTrack(track *atom.Track) (record *atom.AVCDecoderConfRecord) {
|
||||
if media := track.Media; media != nil {
|
||||
if info := media.Info; info != nil {
|
||||
if sample := info.Sample; sample != nil {
|
||||
if desc := sample.SampleDesc; desc != nil {
|
||||
if avc1 := desc.Avc1Desc; avc1 != nil {
|
||||
if conf := avc1.Conf; conf != nil {
|
||||
return &conf.Record
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ProbeFile(filename string) (err error) {
|
||||
var osfile *os.File
|
||||
if osfile, err = os.Open(filename); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var finfo os.FileInfo
|
||||
if finfo, err = osfile.Stat(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dumper := &atom.Dumper{}
|
||||
var moov *atom.Movie
|
||||
|
||||
lr := &io.LimitedReader{R: osfile, N: finfo.Size()}
|
||||
for lr.N > 0 {
|
||||
var ar *io.LimitedReader
|
||||
|
||||
var cc4 string
|
||||
if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil {
|
||||
log.Println("read atom failed")
|
||||
return
|
||||
}
|
||||
|
||||
if cc4 == "moov" {
|
||||
if moov, err = atom.ReadMovie(ar); err != nil {
|
||||
log.Println("read '%s' atom failed", cc4)
|
||||
return
|
||||
}
|
||||
if false {
|
||||
atom.WalkMovie(dumper, moov)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("atom:", cc4)
|
||||
}
|
||||
|
||||
if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if moov == nil {
|
||||
err = fmt.Errorf("'moov' atom not found")
|
||||
log.Println("'moov' atom not found")
|
||||
return
|
||||
}
|
||||
|
||||
if len(moov.Tracks) > 0 {
|
||||
track := moov.Tracks[0]
|
||||
record := getAVCDecoderConfRecordByTrack(track)
|
||||
|
||||
if record != nil && len(record.SPS) > 0 {
|
||||
sps := record.SPS[0]
|
||||
if len(sps) > 1 {
|
||||
sps = sps[1:]
|
||||
log.Println(hex.Dump(sps))
|
||||
var info *atom.H264SPSInfo
|
||||
if info, err = atom.ParseH264SPS(sps); err != nil {
|
||||
return
|
||||
}
|
||||
log.Println(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestConvert(filename string) (err error) {
|
||||
var osfile *os.File
|
||||
if osfile, err = os.Open(filename); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var finfo os.FileInfo
|
||||
if finfo, err = osfile.Stat(); err != nil {
|
||||
return
|
||||
}
|
||||
log.Println("filesize", finfo.Size())
|
||||
|
||||
lr := &io.LimitedReader{R: osfile, N: finfo.Size()}
|
||||
|
||||
var outfile *os.File
|
||||
if outfile, err = os.Create(filename+".out.mp4"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for lr.N > 0 {
|
||||
var ar *io.LimitedReader
|
||||
|
||||
var cc4 string
|
||||
if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cc4 == "moov" {
|
||||
|
||||
curPos, _ := outfile.Seek(0, 1)
|
||||
origSize := ar.N+8
|
||||
var moov *atom.Movie
|
||||
if moov, err = atom.ReadMovie(ar); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//log.Println(moov.Tracks[0].Media.Info.Data.Refer)
|
||||
|
||||
if err = atom.WriteMovie(outfile, moov); err != nil {
|
||||
return
|
||||
}
|
||||
curPosAfterRead, _ := outfile.Seek(0, 1)
|
||||
bytesWritten := curPosAfterRead - curPos
|
||||
|
||||
log.Println("regen moov", "tracks nr", len(moov.Tracks),
|
||||
"origSize", origSize, "bytesWritten", bytesWritten,
|
||||
)
|
||||
|
||||
padSize := origSize - bytesWritten - 8
|
||||
aw, _ := atom.WriteAtomHeader(outfile, "free")
|
||||
atom.WriteDummy(outfile, int(padSize))
|
||||
aw.Close()
|
||||
|
||||
} else {
|
||||
|
||||
outcc4 := cc4
|
||||
if outcc4 != "mdat" {
|
||||
log.Println("omit", cc4)
|
||||
outcc4 = "free"
|
||||
} else {
|
||||
log.Println("copy", cc4)
|
||||
}
|
||||
|
||||
var aw *atom.Writer
|
||||
if aw, err = atom.WriteAtomHeader(outfile, outcc4); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.CopyN(aw, ar, ar.N); err != nil {
|
||||
return
|
||||
}
|
||||
if err = aw.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//log.Println("atom", cc4, "left", lr.N)
|
||||
//atom.ReadDummy(ar, int(ar.N))
|
||||
}
|
||||
|
||||
if err = outfile.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user