diff --git a/architecture.jpg b/architecture.jpg index a4abbe7..95cc5be 100644 Binary files a/architecture.jpg and b/architecture.jpg differ diff --git a/example/default/config.yaml b/example/default/config.yaml index fb5c7db..fc58707 100755 --- a/example/default/config.yaml +++ b/example/default/config.yaml @@ -25,12 +25,12 @@ mp4: # enable: false publish: delayclosetimeout: 3s - # onpub: - # record: - # ^live/.+: - # fragment: 10s - # filepath: record/$0 - # type: mp4 + onpub: + record: + ^live/.+: + fragment: 10s + filepath: record/$0 + # type: fmp4 onsub: pull: ^vod_mp4_\d+/(.+)$: $1 diff --git a/plugin/hls/hls.js/fmp4.html b/plugin/hls/hls.js/fmp4.html index 0971106..23541b3 100644 --- a/plugin/hls/hls.js/fmp4.html +++ b/plugin/hls/hls.js/fmp4.html @@ -142,9 +142,9 @@ try { // Try different codec combinations const codecConfigs = [ - 'video/mp4; codecs="avc1.64001f"', // Video only + //'video/mp4; codecs="avc1.64001f"', // Video only 'video/mp4; codecs="avc1.64001f,mp4a.40.2"', // Video + AAC - 'video/mp4' // Let the browser figure it out + // 'video/mp4' // Let the browser figure it out ]; let sourceBufferCreated = false; diff --git a/plugin/mp4/pkg/bits/reader.go b/plugin/mp4/pkg/bits/reader.go deleted file mode 100644 index 1273219..0000000 --- a/plugin/mp4/pkg/bits/reader.go +++ /dev/null @@ -1,59 +0,0 @@ -package bits - -// Reader is a bit stream reader -type Reader struct { - Data []byte - Offset int -} - -// Skip skips n bits -func (r *Reader) Skip(n int) { - r.Offset += n -} - -// ReadBit reads a single bit -func (r *Reader) ReadBit() (uint, error) { - if r.Offset/8 >= len(r.Data) { - return 0, nil - } - b := r.Data[r.Offset/8] - v := (b >> (7 - (r.Offset % 8))) & 0x01 - r.Offset++ - return uint(v), nil -} - -// ReadExpGolomb reads an Exp-Golomb code -func (r *Reader) ReadExpGolomb() (uint, error) { - leadingZeroBits := 0 - for { - b, err := r.ReadBit() - if err != nil { - return 0, err - } - if b == 1 { - break - } - leadingZeroBits++ - } - - result := uint(1<> 1) + (val & 0x01)) * uint(sign) - return int(val), nil -} diff --git a/plugin/mp4/pkg/box/box.go b/plugin/mp4/pkg/box/box.go index 90a9a61..a88d970 100644 --- a/plugin/mp4/pkg/box/box.go +++ b/plugin/mp4/pkg/box/box.go @@ -172,8 +172,8 @@ func WriteTo(w io.Writer, box ...IBox) (n int64, err error) { if err != nil { return } - if n1 + n2 != int64(b.Size()) { - panic(fmt.Sprintf("write to %s size error, %d != %d", b.Type(), n1 + n2, b.Size())) + if n1+n2 != int64(b.Size()) { + panic(fmt.Sprintf("write to %s size error, %d != %d", b.Type(), n1+n2, b.Size())) } n += n1 + n2 } @@ -193,7 +193,7 @@ func ReadFrom(r io.Reader) (box IBox, err error) { if !exists { return nil, fmt.Errorf("unknown box type: %s", baseBox.typ) } - b := reflect.New(t).Interface().(IBox) + b := reflect.New(t.Elem()).Interface().(IBox) var payload []byte if baseBox.size == 1 { if _, err = io.ReadFull(r, tmp[:]); err != nil { @@ -203,8 +203,10 @@ func ReadFrom(r io.Reader) (box IBox, err error) { } else { payload = make([]byte, baseBox.size-BasicBoxLen) } - _, err = io.ReadFull(r, payload) + if err != nil { + return nil, err + } boxHeader := b.Header() switch header := boxHeader.(type) { case *BaseBox: @@ -216,6 +218,9 @@ func ReadFrom(r io.Reader) (box IBox, err error) { header.Flags = [3]byte(payload[1:4]) box, err = b.Unmarshal(payload[4:]) } + if err == io.EOF { + return box, nil + } return } diff --git a/plugin/mp4/pkg/box/ctts.go b/plugin/mp4/pkg/box/ctts.go index 37263e3..f193afe 100644 --- a/plugin/mp4/pkg/box/ctts.go +++ b/plugin/mp4/pkg/box/ctts.go @@ -56,5 +56,5 @@ func (box *CTTSBox) Unmarshal(buf []byte) (IBox, error) { } func init() { - RegisterBox[CTTSBox](TypeCTTS) + RegisterBox[*CTTSBox](TypeCTTS) } diff --git a/plugin/mp4/pkg/box/dinf.go b/plugin/mp4/pkg/box/dinf.go index 8a8e5ef..123fb15 100644 --- a/plugin/mp4/pkg/box/dinf.go +++ b/plugin/mp4/pkg/box/dinf.go @@ -105,9 +105,8 @@ func (box *DataInformationBox) WriteTo(w io.Writer) (n int64, err error) { return WriteTo(w, box.Dref) } -func (box *DataInformationBox) Unmarshal(buf []byte) (IBox, error) { - r := bytes.NewReader(buf) - b, err := ReadFrom(r) +func (box *DataInformationBox) Unmarshal(buf []byte) (b IBox, err error) { + b, err = ReadFrom(bytes.NewReader(buf)) if err != nil { return nil, err } diff --git a/plugin/mp4/pkg/box/hdlr.go b/plugin/mp4/pkg/box/hdlr.go index c26313b..cf5308e 100644 --- a/plugin/mp4/pkg/box/hdlr.go +++ b/plugin/mp4/pkg/box/hdlr.go @@ -91,3 +91,7 @@ func MakeHdlrBox(hdt HandlerType) *HandlerBox { } return hdlr } + +func init() { + RegisterBox[*HandlerBox](TypeHDLR) +} diff --git a/plugin/mp4/pkg/box/mdia.go b/plugin/mp4/pkg/box/mdia.go index 9f39f08..1fdd3ea 100644 --- a/plugin/mp4/pkg/box/mdia.go +++ b/plugin/mp4/pkg/box/mdia.go @@ -16,11 +16,12 @@ func (m *MdiaBox) WriteTo(w io.Writer) (n int64, err error) { return WriteTo(w, m.MDHD, m.MINF, m.HDLR) } -func (m *MdiaBox) Unmarshal(buf []byte) (IBox, error) { +func (m *MdiaBox) Unmarshal(buf []byte) (b IBox, err error) { + r := bytes.NewReader(buf) for { - b, err := ReadFrom(bytes.NewReader(buf)) + b, err = ReadFrom(r) if err != nil { - return nil, err + return m, err } switch box := b.(type) { case *MediaHeaderBox: @@ -46,12 +47,10 @@ func (m *MediaInformationBox) WriteTo(w io.Writer) (n int64, err error) { return WriteTo(w, m.VMHD, m.SMHD, m.HMHD, m.STBL, m.DINF) } -func (m *MediaInformationBox) Unmarshal(buf []byte) (IBox, error) { - for { - b, err := ReadFrom(bytes.NewReader(buf)) - if err != nil { - return nil, err - } +func (m *MediaInformationBox) Unmarshal(buf []byte) (b IBox, err error) { + r := bytes.NewReader(buf) + for err == nil { + b, err = ReadFrom(r) switch box := b.(type) { case *VideoMediaHeaderBox: m.VMHD = box @@ -65,8 +64,10 @@ func (m *MediaInformationBox) Unmarshal(buf []byte) (IBox, error) { m.DINF = box } } + return m, err } func init() { RegisterBox[*MdiaBox](TypeMDIA) + RegisterBox[*MediaInformationBox](TypeMINF) } diff --git a/plugin/mp4/pkg/box/moov.go b/plugin/mp4/pkg/box/moov.go index df506be..a25653c 100644 --- a/plugin/mp4/pkg/box/moov.go +++ b/plugin/mp4/pkg/box/moov.go @@ -30,11 +30,11 @@ func (m *MoovBox) WriteTo(w io.Writer) (n int64, err error) { } func (m *MoovBox) Unmarshal(buf []byte) (IBox, error) { + r := bytes.NewReader(buf) for { - b, err := ReadFrom(bytes.NewReader(buf)) - + b, err := ReadFrom(r) if err != nil { - return nil, err + return m, err } switch box := b.(type) { case *TrakBox: @@ -51,17 +51,19 @@ func (e *EdtsBox) WriteTo(w io.Writer) (n int64, err error) { return WriteTo(w, e.Elst) } -func (e *EdtsBox) Unmarshal(buf []byte) (IBox, error) { - for { - b, err := ReadFrom(bytes.NewReader(buf)) +func (e *EdtsBox) Unmarshal(buf []byte) (b IBox, err error) { + r := bytes.NewReader(buf) + for err == nil { + b, err = ReadFrom(r) if err != nil { - return nil, err + return e, err } switch box := b.(type) { case *EditListBox: e.Elst = box } } + return e, err } func init() { diff --git a/plugin/mp4/pkg/box/stsd.go b/plugin/mp4/pkg/box/stsd.go index 9aea840..a48bd02 100644 --- a/plugin/mp4/pkg/box/stsd.go +++ b/plugin/mp4/pkg/box/stsd.go @@ -210,9 +210,9 @@ func (visual *VisualSampleEntry) Unmarshal(buf []byte) (IBox, error) { visual.FrameCount = binary.BigEndian.Uint16(buf[16:]) copy(visual.Compressorname[:], buf[18:50]) visual.Depth = binary.BigEndian.Uint16(buf[50:]) - - if len(buf) > 52 { - box, err := ReadFrom(bytes.NewReader(buf[52:])) + // 52 pre-defined + if len(buf) > 54 { + box, err := ReadFrom(bytes.NewReader(buf[54:])) if err != nil { return nil, err } diff --git a/plugin/mp4/pkg/box/trak.go b/plugin/mp4/pkg/box/trak.go index bf42087..8afee7d 100644 --- a/plugin/mp4/pkg/box/trak.go +++ b/plugin/mp4/pkg/box/trak.go @@ -38,12 +38,10 @@ func (t *TrakBox) WriteTo(w io.Writer) (n int64, err error) { return WriteTo(w, t.MDIA, t.EDTS, t.TKHD) } -func (t *TrakBox) Unmarshal(buf []byte) (IBox, error) { - for { - b, err := ReadFrom(bytes.NewReader(buf)) - if err != nil { - return t, err - } +func (t *TrakBox) Unmarshal(buf []byte) (b IBox, err error) { + r := bytes.NewReader(buf) + for err == nil { + b, err = ReadFrom(r) switch box := b.(type) { case *MdiaBox: t.MDIA = box @@ -53,6 +51,7 @@ func (t *TrakBox) Unmarshal(buf []byte) (IBox, error) { t.TKHD = box } } + return t, err } // ParseSamples parses the sample table and builds the sample list diff --git a/plugin/mp4/pkg/demuxer.go b/plugin/mp4/pkg/demuxer.go index 404026a..3958c5d 100644 --- a/plugin/mp4/pkg/demuxer.go +++ b/plugin/mp4/pkg/demuxer.go @@ -50,6 +50,7 @@ type ( IsFragment bool // pssh []*PsshBox moov *MoovBox + mdat *MediaDataBox QuicTime bool } ) @@ -94,11 +95,14 @@ func (d *Demuxer) Demux() (err error) { // } // return // } - + var b IBox for { - b, err := box.ReadFrom(d.reader) + b, err = box.ReadFrom(d.reader) if err != nil { - break + if err == io.EOF { + break + } + return err } switch box := b.(type) { case *FileTypeBox: @@ -107,6 +111,7 @@ func (d *Demuxer) Demux() (err error) { } case *FreeBox: case *MediaDataBox: + d.mdat = box case *MoovBox: if box.MVEX != nil { d.IsFragment = true diff --git a/plugin/mp4/pkg/demuxer_test.go b/plugin/mp4/pkg/demuxer_test.go new file mode 100644 index 0000000..577312f --- /dev/null +++ b/plugin/mp4/pkg/demuxer_test.go @@ -0,0 +1,76 @@ +package mp4 + +import ( + "fmt" + "io" + "os" + "reflect" + "strings" + "testing" + + "m7s.live/v5/plugin/mp4/pkg/box" +) + +// TestDemuxerBoxTree tests the Demuxer by reading a test MP4 file and printing the box tree structure. +func TestDemuxerBoxTree(t *testing.T) { + // Open the test mp4 file. It is assumed to be located in 'testdata/test_regular.mp4'. + f, err := os.Open("/Users/dexter/project/v5/monibuca/example/default/dump/test_regular.mp4") + if err != nil { + t.Fatalf("failed to open test mp4 file: %v", err) + } + defer f.Close() + + // Create a new Demuxer with the file reader + d := NewDemuxer(f) + + // Call Demux to process the file; we don't use the result directly here. + err = d.Demux() + if err != nil { + t.Fatalf("demuxing failed: %v", err) + } + + // Reset the file pointer to the beginning to re-read boxes for tree display + if _, err := f.Seek(0, 0); err != nil { + t.Fatalf("failed to seek to beginning: %v", err) + } + + fmt.Println("MP4 Box Tree:") + // Read and print each top-level box + for { + b, err := box.ReadFrom(f) + if err != nil { + if err == io.EOF { + break + } + t.Fatalf("failed to read box: %v", err) + } + printBox(b, 0) + } +} + +// printBox prints a box's type and size, and recursively prints its children if available. +func printBox(b interface{}, indent int) { + ind := strings.Repeat(" ", indent) + // Determine the box type name using reflection + typeOfBox := reflect.TypeOf(b) + if typeOfBox.Kind() == reflect.Ptr { + typeOfBox = typeOfBox.Elem() + } + // Try to get the size from a method Size() int + var size int + if s, ok := b.(interface{ Size() int }); ok { + size = s.Size() + } + fmt.Printf("%s%s (size: %d)\n", ind, typeOfBox.Name(), size) + + // If the box is a container and has child boxes, print them recursively. + if container, ok := b.(interface{ ChildrenBoxes() []interface{} }); ok { + for _, child := range container.ChildrenBoxes() { + printBox(child, indent+1) + } + } else if container, ok := b.(interface{ ChildrenBoxes() []any }); ok { + for _, child := range container.ChildrenBoxes() { + printBox(child, indent+1) + } + } +}